/* A reverse-engineered implementation of the EasyCard data format */ /* (C) 2010 by Harald Welte * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* System includes */ #include #include #include #include #include #include #include #include #include #include #include #include /* libnfc includes */ #include #include #include "mifare_classic.h" /* Easycard specific includes */ #include "easycard.h" /* convert from 3byte easycard-timestamp to time_t */ time_t easy_timestamp2time(const uint8_t *easy_ts) { return (easy_ts[2] << 16 | easy_ts[1] << 8 | easy_ts[0]) << 8; } /* apply a delta (positive or negative) to a EasyCard log record */ int easy_update_log_rec(struct easy_log_rec *elr, int16_t delta) { int32_t sum = elr->amount + delta; int32_t remaining = elr->remaining + delta; if ((sum < 0 || sum > 0xffff) || (remaining < 0 || remaining > 0xffff)) return -ERANGE; elr->amount = sum; elr->remaining = remaining; return 0; } /* apply a delta to the 'sum of day' record in Sector 15 Block 2 */ int easy_update_sum_of_day(struct easy_sect15blk2 *s15b2, int16_t delta) { int32_t sum = s15b2->sum_of_day + delta; if (sum < 0 || sum > 0xffff) return -ERANGE; s15b2->sum_of_day = sum; return 0; } static char tsbuf[64]; char *easy_asc_timestamp(const uint8_t *timestamp) { time_t t_time = easy_timestamp2time(timestamp); struct tm *t_tm = gmtime(&t_time); memset(tsbuf, 0, sizeof(tsbuf)); snprintf(tsbuf, sizeof(tsbuf), "%4u-%02u-%02u %02u:%02u", t_tm->tm_year+1900, t_tm->tm_mon+1, t_tm->tm_mday, t_tm->tm_hour, t_tm->tm_min); return tsbuf; } static const struct mfcl_addr easy_trans_log_addrs[] = { { .sector = 2, .block = 2, }, /* first block of sector 3 is not a log record */ { .sector = 3, .block = 1, }, { .sector = 3, .block = 2, }, { .sector = 4, .block = 0, }, { .sector = 4, .block = 1, }, { .sector = 4, .block = 2, }, { .sector = 5, .block = 0, }, { .sector = 5, .block = 1, }, { .sector = 5, .block = 2, }, }; /* Dump a single transaction log record */ void easy_dump_log_rec(const struct easy_log_rec *elr) { /* Skip empty records */ if (!elr->trans_id) return; printf("%s | %02x | %4u NTD | %4u NTD | %-15s", easy_asc_timestamp(elr->timestamp), elr->trans_id, elr->amount, elr->remaining, get_value_string(easy_tt_names, elr->trans_type)); switch (elr->trans_type) { case EASY_TT_MRT_ENTER: case EASY_TT_MRT_REENTER: case EASY_TT_MRT_EXIT: case EASY_TT_RECHARGE: printf(" | %s\n", get_value_string(taipei_mrt_stn_id, elr->station_code)); break; default: printf("\n"); } } void easy_log_rec_hdr(void) { /* "2010-01-01 01:01 | TID| Amount | Balance | */ puts("Timestamp | TID| Amount | Balance | Type | MRT Station"); puts("---------------------------------------------------------------------------"); } /* Dump the entire transaction log of the given mifare card */ void easy_dump_log(mifare_tag *mft) { unsigned int i; easy_log_rec_hdr(); for (i = 0; i < ARRAY_SIZE(easy_trans_log_addrs); i++) { unsigned int sect = easy_trans_log_addrs[i].sector; unsigned int block = easy_trans_log_addrs[i].block; unsigned int block_base = sect * 4; void *data = mft->amb[block_base+block].mbd.abtData; #if 0 printf("Dumping Sector %u Block %u (0x%x)\n", sect, block, (block_base+sect)*16); #endif easy_dump_log_rec(data); } } static struct easy_log_rec *get_last_trans(mifare_tag *mft, uint8_t type) { unsigned int i; time_t last_trans_time = 0; struct easy_log_rec *last_trans = NULL; for (i = 0; i < ARRAY_SIZE(easy_trans_log_addrs); i++) { unsigned int sect = easy_trans_log_addrs[i].sector; unsigned int block = easy_trans_log_addrs[i].block; unsigned int block_base = sect * 4; struct easy_log_rec *elr = (struct easy_log_rec *) mft->amb[block_base+block].mbd.abtData; if (elr->trans_type != type) continue; if (easy_timestamp2time(elr->timestamp) > last_trans_time) { last_trans = elr; last_trans_time = easy_timestamp2time(elr->timestamp); } } return last_trans; } #define MAX_NTD_PER_PURCHASE 1000 #define MAX_NTD_PER_DAY 3000 #define MAX_AMOUNT_PER_CARD 5000 /* positive value: make it more expensive. negative: cheaper */ int easy_alter_last_purchase(mifare_tag *mft, int16_t delta) { struct easy_log_rec *elr; struct mfcl_value_block *val_block = (struct mfcl_value_block *) mft->amb[2*4].mbd.abtData; struct mfcl_value_block *val_block2 = (struct mfcl_value_block *) mft->amb[2*4+1].mbd.abtData; struct easy_sect15blk2 *s15b2 = (struct easy_sect15blk2 *) mft->amb[15*4+2].mbd.abtData; time_t t_time; struct tm *t_tm; int rc; /* Step 1: find last purchasing transaction */ elr = get_last_trans(mft, EASY_TT_PURCHASE); if (!elr) return -ENOENT; printf("Found last transaction log record:\n"); easy_log_rec_hdr(); easy_dump_log_rec(elr); /* Step 2: check if delta is within range */ if (delta >= 0) { if (elr->amount + delta > MAX_NTD_PER_PURCHASE) { fprintf(stderr, "%u NTD + delta %d would exceed " "maximum purchase value of %u\n", elr->amount, delta, MAX_NTD_PER_PURCHASE); return -ERANGE; } if (elr->remaining < delta) { fprintf(stderr, "Delta of %d is more than what is " "left on the card (%u)\n", delta, elr->remaining); return -ERANGE; } } else { if (elr->amount < delta) { fprintf(stderr, "Cannot make purchase of %u NTD " "cheaper by %d\n", elr->amount, delta); return -ERANGE; } if (elr->remaining + abs(delta) > MAX_AMOUNT_PER_CARD) { fprintf(stderr, "%u + %u are in excess of maximum " "value the card can hold (%u)\n", elr->remaining, abs(delta), MAX_AMOUNT_PER_CARD); return -ERANGE; } } /* Step 3: Make sure the VALUE block and the 'remaining after trans' * agree */ if (elr->remaining != val_block->value) { fprintf(stderr, "Amount remaining after last transaction (%u) " "and VALUE block (%u) are inconsistent\n", elr->remaining, val_block->value); return -EIO; } /* Step 4: Actually apply the delta to the transaction */ rc = easy_update_log_rec(elr, delta); if (rc < 0) return rc; printf("=> Modified log record:\n"); easy_dump_log_rec(elr); /* Step 5: Actually apply the delta to the value blocks */ /* we need to _subtract_ the value... */ rc = mfcl_update_value_block(val_block, -delta); if (rc < 0) return rc; rc = mfcl_update_value_block(val_block2, -delta); if (rc < 0) return rc; /* Step 6: Update the 'sum of the day', if needed */ t_time = easy_timestamp2time(elr->timestamp); t_tm = gmtime(&t_time); if (s15b2->day_of_month == t_tm->tm_mday) easy_update_sum_of_day(s15b2, delta); return rc; } /* positive value: make it more expensive. negative: cheaper */ int easy_alter_last_recharge(mifare_tag *mft, int16_t delta) { struct easy_log_rec *elr; struct mfcl_value_block *val_block = (struct mfcl_value_block *) mft->amb[2*4].mbd.abtData; struct mfcl_value_block *val_block2 = (struct mfcl_value_block *) mft->amb[2*4+1].mbd.abtData; int rc; /* Step 1: find last recharging transaction */ elr = get_last_trans(mft, EASY_TT_RECHARGE); if (!elr) { fprintf(stderr, "Cannot find a recharge transaction\n"); return -ENOENT; } printf("Found last transaction log record:\n"); easy_log_rec_hdr(); easy_dump_log_rec(elr); /* Step 2: check if delta is within range */ if (elr->remaining + delta > MAX_AMOUNT_PER_CARD) { fprintf(stderr, "%u plus the delta of %d would exceed the " "per-card maximum of %u\n", elr->remaining, delta, MAX_AMOUNT_PER_CARD); return -ERANGE; } /* Step 3: Make sure the VALUE block and the 'remaining after trans' * agree */ if (elr->remaining != val_block->value) { fprintf(stderr, "Amount remaining after last transaction (%u) " "and VALUE block (%u) are inconsistent\n", elr->remaining, val_block->value); return -EIO; } /* Step 4: Actually apply the delta to the transaction */ rc = easy_update_log_rec(elr, delta); if (rc < 0) return rc; printf("=> Modified log record:\n"); easy_dump_log_rec(elr); /* Step 5: Actually apply the delta to the value blocks */ rc = mfcl_update_value_block(val_block, delta); if (rc < 0) return rc; rc = mfcl_update_value_block(val_block2, delta); if (rc < 0) return rc; return rc; }