1 //-----------------------------------------------------------------------------
2 // Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // See LICENSE.txt for the text of the license.
15 //-----------------------------------------------------------------------------
16 // Commands for KS X 6924 transit cards (T-Money, Snapper+)
17 //-----------------------------------------------------------------------------
18 // This is used in T-Money (South Korea) and Snapper plus (Wellington, New
22 // - https://github.com/micolous/metrodroid/wiki/T-Money (in English)
23 // - https://github.com/micolous/metrodroid/wiki/Snapper (in English)
24 // - https://kssn.net/StdKS/ks_detail.asp?k1=X&k2=6924-1&k3=4
25 // (KS X 6924, only available in Korean)
26 // - http://www.tta.or.kr/include/Download.jsp?filename=stnfile/TTAK.KO-12.0240_%5B2%5D.pdf
27 // (TTAK.KO 12.0240, only available in Korean)
28 //-----------------------------------------------------------------------------
31 #include "cmdhfksx6924.h"
43 #include "proxmark3.h"
44 #include "cliparser.h"
45 #include "ksx6924/ksx6924core.h"
47 #include "iso7816/apduinfo.h"
49 #include "protocols.h" // ISO7816 APDU return codes
51 static int CmdHelp(const char *Cmd
);
53 static int get_and_print_balance(void) {
55 if (KSX6924GetBalance(&balance
) == false) {
56 PrintAndLogEx(ERR
, "Error getting balance");
60 PrintAndLogEx(SUCCESS
, "Current balance: " _YELLOW_("%u") " won/cents", balance
);
64 static int CmdHFKSX6924Balance(const char *Cmd
) {
65 CLIParserContext
*ctx
;
66 CLIParserInit(&ctx
, "hf ksx6924 balance",
67 "Gets the current purse balance",
68 "hf ksx6924 balance\n");
72 arg_lit0("k", "keep", "keep field ON for next command"),
73 arg_lit0("a", "apdu", "Show APDU requests and responses"),
76 CLIExecWithReturn(ctx
, Cmd
, argtable
, true);
78 bool keep
= arg_get_lit(ctx
, 1);
79 bool APDULogging
= arg_get_lit(ctx
, 2);
82 SetAPDULogging(APDULogging
);
84 if (KSX6924TrySelect()) {
85 get_and_print_balance();
95 static int CmdHFKSX6924Info(const char *Cmd
) {
96 CLIParserContext
*ctx
;
97 CLIParserInit(&ctx
, "hf ksx6924 info",
98 "Get info about a KS X 6924 transit card.\n"
99 "This application is used by T-Money (South Korea) and\n"
100 "Snapper+ (Wellington, New Zealand).",
101 "hf ksx6924 info\n");
105 arg_lit0("k", "keep", "keep field ON for next command"),
106 arg_lit0("a", "apdu", "Show APDU requests and responses"),
109 CLIExecWithReturn(ctx
, Cmd
, argtable
, true);
111 bool keep
= arg_get_lit(ctx
, 1);
112 bool APDULogging
= arg_get_lit(ctx
, 2);
115 SetAPDULogging(APDULogging
);
118 uint8_t buf
[APDU_RES_LEN
] = {0};
121 int res
= KSX6924Select(true, true, buf
, sizeof(buf
), &len
, &sw
);
123 if (res
|| (len
== 0)) {
130 if (sw
!= ISO7816_OK
) {
132 PrintAndLogEx(INFO
, "Not a KS X 6924 card! APDU response: %04x - %s",
133 sw
, GetAPDUCodeDescription(sw
>> 8, sw
& 0xff));
135 PrintAndLogEx(ERR
, "APDU exchange error. Card returns 0x0000.");
141 // PrintAndLogEx(DEBUG, "APDU response: %s", sprint_hex(buf, len));
143 // FCI Response is a BER-TLV, we are interested in tag 6F,B0 only.
144 const uint8_t *p
= buf
;
146 memset(&fci_tag
, 0, sizeof(fci_tag
));
149 memset(&fci_tag
, 0, sizeof(fci_tag
));
150 bool ret
= tlv_parse_tl(&p
, &len
, &fci_tag
);
153 PrintAndLogEx(FAILED
, "Error parsing FCI!");
157 // PrintAndLog("tag %02x, len %d, value %s",
158 // fci_tag.tag, fci_tag.len,
159 // sprint_hex(p, fci_tag.len));
161 if (fci_tag
.tag
== 0x6f) { /* FCI template */
169 if (fci_tag
.tag
!= 0x6f) {
170 PrintAndLogEx(ERR
, "Couldn't find tag 6F (FCI) in SELECT response");
174 // We now are at Tag 6F (FCI template), get Tag B0 inside of it
176 memset(&fci_tag
, 0, sizeof(fci_tag
));
177 bool ret
= tlv_parse_tl(&p
, &len
, &fci_tag
);
180 PrintAndLogEx(ERR
, "Error parsing FCI!");
184 // PrintAndLog("tag %02x, len %d, value %s",
185 // fci_tag.tag, fci_tag.len,
186 // sprint_hex(p, fci_tag.len));
188 if (fci_tag
.tag
== 0xb0) { /* KS X 6924 purse info */
196 if (fci_tag
.tag
!= 0xb0) {
197 PrintAndLogEx(FAILED
, "Couldn't find tag B0 (KS X 6924 purse info) in FCI");
201 struct ksx6924_purse_info purseInfo
;
202 bool ret
= KSX6924ParsePurseInfo(p
, fci_tag
.len
, &purseInfo
);
205 PrintAndLogEx(FAILED
, "Error parsing KS X 6924 purse info");
209 KSX6924PrintPurseInfo(&purseInfo
);
211 get_and_print_balance();
220 static int CmdHFKSX6924Select(const char *Cmd
) {
221 CLIParserContext
*ctx
;
222 CLIParserInit(&ctx
, "hf ksx6924 select",
223 "Selects KS X 6924 application, and leaves field up",
224 "hf ksx6924 select\n");
228 arg_lit0("a", "apdu", "Show APDU requests and responses"),
231 CLIExecWithReturn(ctx
, Cmd
, argtable
, true);
233 bool APDULogging
= arg_get_lit(ctx
, 1);
235 SetAPDULogging(APDULogging
);
237 if (KSX6924TrySelect()) {
238 PrintAndLogEx(SUCCESS
, "Card is selected and field is up");
240 // Wrong app, drop field.
247 static int CmdHFKSX6924Initialize(const char *Cmd
) {
248 CLIParserContext
*ctx
;
249 CLIParserInit(&ctx
, "hf ksx6924 init",
250 "Perform transaction initialization with Mpda (Money of Purchase Transaction)",
251 "hf ksx6924 init 000003e8 -> Mpda\n");
255 arg_lit0("k", "keep", "keep field ON for next command"),
256 arg_lit0("a", "apdu", "Show APDU requests and responses"),
257 arg_str1(NULL
, NULL
, "<Mpda 4 bytes hex>", NULL
),
260 CLIExecWithReturn(ctx
, Cmd
, argtable
, true);
262 bool keep
= arg_get_lit(ctx
, 1);
263 bool APDULogging
= arg_get_lit(ctx
, 2);
265 uint8_t data
[APDU_RES_LEN
] = {0};
267 CLIGetHexWithReturn(ctx
, 3, data
, &datalen
);
270 SetAPDULogging(APDULogging
);
273 PrintAndLogEx(WARNING
, "Mpda parameter must be 4 bytes long (eg: 000003e8)");
277 // try selecting card
278 if (KSX6924TrySelect() == false) {
282 uint8_t resp
[APDU_RES_LEN
] = {0};
284 if (KSX6924InitializeCard(data
[0], data
[1], data
[2], data
[3], resp
, &resp_len
) == false) {
289 struct ksx6924_initialize_card_response initCardResponse
;
290 bool ret
= KSX6924ParseInitializeCardResponse(r
, resp_len
, &initCardResponse
);
293 PrintAndLogEx(FAILED
, "Error parsing KS X 6924 initialize card response");
297 KSX6924PrintInitializeCardResponse(&initCardResponse
);
307 static int CmdHFKSX6924PRec(const char *Cmd
) {
308 CLIParserContext
*ctx
;
309 CLIParserInit(&ctx
, "hf ksx6924 prec",
310 "Executes proprietary read record command.\n"
311 "Data format is unknown. Other records are available with 'emv getrec'.\n",
312 "hf ksx6924 prec 0b -> read proprietary record 0x0b");
316 arg_lit0("k", "keep", "keep field ON for next command"),
317 arg_lit0("a", "apdu", "Show APDU requests and responses"),
318 arg_str1(NULL
, NULL
, "<record 1byte HEX>", NULL
),
321 CLIExecWithReturn(ctx
, Cmd
, argtable
, true);
323 bool keep
= arg_get_lit(ctx
, 1);
324 bool APDULogging
= arg_get_lit(ctx
, 2);
326 uint8_t data
[APDU_RES_LEN
] = {0};
328 CLIGetHexWithReturn(ctx
, 3, data
, &datalen
);
331 SetAPDULogging(APDULogging
);
334 PrintAndLogEx(WARNING
, "Record parameter must be 1 byte long (eg: 0f)");
338 if (KSX6924TrySelect() == false) {
342 PrintAndLogEx(SUCCESS
, "Getting record %02x ...", data
[0]);
344 uint8_t recordData
[0x10] = {0};
345 if (KSX6924ProprietaryGetRecord(data
[0], recordData
, sizeof(recordData
))) {
346 PrintAndLogEx(SUCCESS
, " %s", sprint_hex(recordData
, sizeof(recordData
)));
348 PrintAndLogEx(FAILED
, "Error getting record");
358 static command_t CommandTable
[] = {
359 {"help", CmdHelp
, AlwaysAvailable
, "This help"},
360 {"select", CmdHFKSX6924Select
, IfPm3Iso14443a
, "Select application, and leave field up"},
361 {"info", CmdHFKSX6924Info
, IfPm3Iso14443a
, "Get info about a KS X 6924 (T-Money, Snapper+) transit card"},
362 {"balance", CmdHFKSX6924Balance
, IfPm3Iso14443a
, "Get current purse balance"},
363 {"init", CmdHFKSX6924Initialize
, IfPm3Iso14443a
, "Perform transaction initialization with Mpda"},
364 {"prec", CmdHFKSX6924PRec
, IfPm3Iso14443a
, "Send proprietary get record command (CLA=90, INS=4C)"},
365 {NULL
, NULL
, NULL
, NULL
}
368 static int CmdHelp(const char *Cmd
) {
369 (void)Cmd
; // Cmd is not used so far
370 CmdsHelp(CommandTable
);
374 int CmdHFKSX6924(const char *Cmd
) {
375 clearCommandBuffer();
376 return CmdsParse(CommandTable
, Cmd
);