|
| 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 | + |
0 commit comments