Skip to content

Instantly share code, notes, and snippets.

@roman-yepishev
Created December 15, 2011 10:05
Show Gist options
  • Save roman-yepishev/1480588 to your computer and use it in GitHub Desktop.
Save roman-yepishev/1480588 to your computer and use it in GitHub Desktop.
NSMEP expiration and balance check
#!/usr/bin/perl
# Copyright 2011 Roman Yepishev <roman.yepishev@yandex.ua>
#
# Fetch various NSMEP information from card
#
# WARNING: This application is provided AS IS. It can render your NSMEP card unusable
# or cause malfunction. The author assumes no liability for any of the damage
# resulted from using this script.
# This code is based on ASSUMPTIONS about the location of the data in the
# APDUs, there is no public information about the format of the fields.
use 5.012;
use warnings;
# use magic;
use Chipcard::PCSC;
use Chipcard::PCSC::Card;
my $EPOCH_START = 1996;
my $KNOWN_BANKS = {
0x00000800 => "Express-Bank",
0x00001000 => "Polycombank",
0x00000500 => "Demark",
};
my $KNOWN_APPS = {
0xdd => "Wallet",
0xde => "Cheque",
};
my $KNOWN_CURRENCIES = {
980 => "UAH",
};
sub main {
my $context = new Chipcard::PCSC();
my @readers = $context->ListReaders();
if (! $readers[0]) {
die "Cannot find SC cardreader";
}
say "Readers: @readers";
my $reader = $readers[0];
my $card = new Chipcard::PCSC::Card($context, $reader,
$Chipcard::PCSC::SCARD_SHARE_EXCLUSIVE);
if (! $card) {
die "Cannot contact card: $Chipcard::PCSC::errno";
}
my $data;
# getting software version
$data = $card->Transmit([0x80, 0xf6, 0x00, 0x01, 0x08]);
my $software = byte2str(@{$data}[0..7]);
# initializing payment system?
$data = $card->Transmit([0x00, 0xb0, 0x83, 0x00, 0x15]);
my $card_issued = parse_date(@{$data}[6..8]);
my $card_expires = parse_date(@{$data}[9..11]);
my $card_issuer = byte2int(@{$data}[13..16]);
print "Card software: $software\n";
printf "Card issued: %s\nCard expires: %s\n",
$card_issued, $card_expires;
print "Card issuer: $KNOWN_BANKS->{$card_issuer}\n";
# Available applications:
$data = $card->Transmit([0xe0, 0x34, 0x00, 0x00, 0x00]);
# 6..10 - expiration
# 24.. - blocks of app descriptions
my $accounts = {};
APP_LIST:
for (my $block=24; $block < @{$data}; $block += 5) {
my $end = $block+4;
my @app = @{$data}[$block..$end];
if ($app[0] == 0xc4) {
# no more applications
last APP_LIST;
}
my $app_id = sprintf("%x %x %x %x %x", @app);
print "Application: $KNOWN_APPS->{$app[0]}: $app_id\n";
my $result = $card->Transmit([0xe0, 0x34, 0x01, 0x00, 0x05,
@app, 0x00]);
$accounts->{$app[0]} = parse_account_info(\@app, $result);
}
for my $account (keys %{$accounts}) {
my $a = $accounts->{$account};
my $c = $a->{'currency'};
print "$KNOWN_APPS->{$account}:\n";
printf " Balance: %.2f $c\n", $a->{'balance'} / 100;
printf " Last transaction: %.2f $c\n", $a->{'last_transaction'} / 100;
printf " Expires: %s\n", $a->{'exp_date'};
print " Limits:\n";
printf " Single Operation: %.2f $c\n", $a->{'single_op_limit'}/100;
printf " Operations per day: %.2f $c\n", $a->{'op_daily_limit'}/100;
printf " Cash Withdrawal: %.2f $c\n", $a->{'cash_op_limit'}/100;
printf " Cash Withdrawal Per Day: %.2f $c\n", $a->{'cash_op_limit'}/100;
printf " Load operation: %.2f $c\n", $a->{'load_limit'}/100;
printf " Max storage: %.2f $c\n", $a->{'max_storage'}/100;
}
# can be shutdown
$card->Transmit([0x00, 0xa4, 0x00, 0x00, 0x02, 0x3f, 0x00, 0x00]);
}
# parse date or datetime value stored in a proprietary way
sub parse_date {
my @data = @_;
my ($year, $month, $date, $hour, $minute, $second);
# interesting format, took a while to figure out the year
$year = hexdec_value($data[0]) + $EPOCH_START;
$month = hexdec_value($data[1]);
$date = hexdec_value($data[2]);
if (@data > 3) {
$hour = hexdec_value($data[3]);
$minute = hexdec_value($data[4]);
$second = hexdec_value($data[5] || 0);
}
else {
$hour = $minute = $second = 0;
}
return sprintf("%04d-%02d-%02d %02d:%02d:%02d",
$year, $month, $date, $hour, $minute, $second);
}
# Wallet and Checque has the same format
sub parse_account_info {
my ($app, $info) = @_;
my @data = @{$info};
my $currency = int(sprintf("%x%x%x", $app->[1], $app->[2], $app->[3]));
my $result = {
'currency' => $KNOWN_CURRENCIES->{$currency},
'balance' => byte2int(@data[9..12]),
'last_transaction' => byte2int(@data[16..19]),
'single_op_limit' => byte2int(@data[24..27]),
'op_daily_limit' => byte2int(@data[28..31]),
'cash_op_limit' => byte2int(@data[32..35]),
'cash_daily_limit' => byte2int(@data[36..39]),
'load_limit' => byte2int(@data[40..43]),
'max_storage' => byte2int(@data[44..47]),
'exp_date' => parse_date(@data[6..10]),
};
return $result;
}
# Convert from 0xaa 0xbb 0xcc 0xdd to 0xaabbccdd
sub byte2int {
return unpack("N", pack("C4", @_));
}
sub byte2str {
return join('', map { chr($_) } @_);
}
# Most of the values come in hex form but they are actually base-10 numbers.
sub hexdec_value {
my ($val) = @_;
return int(sprintf("%x", $val));
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment