Contactless cards in the Kiev subway began to be entered in 2007 (
information on the metro site, in Ukrainian ), but they received widespread adoption only by the end of 2008. To date, there are two main types of tickets: tickets with a validity period, and tickets for the number of trips. Travel cards use
MIFARE Classic 1K contactless cards.
Photo - Metromuseum.netThe vulnerabilities of MIFARE Classic chips became known in 2007. A detailed history of the discovery of vulnerabilities can be found in the
article . The article, though in 2008, is still relevant, and it lists the main stages of finding vulnerabilities. By combining this knowledge, you can see what is recorded in the maps of the Kiev subway on the example of a travel card for the number of trips.
')
Disclaimer: All actions and information described below are provided solely for the purpose of expanding personal horizons, and are not intended for personal gain.Customize the workplace
For reading contactless cards, we will use:
Reader

So, as we will use libnfc, the reader must be chosen from those with which the library is best compatible. We select on the
page of compatibility suitable and we buy. I settled on a SCL3711 reader with a PN533 v2.7 chip (pictured, bought on eBay for $ 35).
Libnfc library
I will describe the build procedure for libnfc for Ubuntu 12.04, for other platforms, detailed installation instructions are
on the site .
Install the dependencies required for building packages and working with SVN (for those who are not installed):
sudo apt-get install subversion dpkg-dev debhelper dh-autoreconf libtool
Install the packages that are needed to build libnfc:
sudo apt-get install libusb-dev libpcsclite-dev
Download and unpack the latest version of the library (1.6.0-rc1), download the files needed to build the deb package from the repository:
wget http: // libnfc.googlecode.com / files / libnfc-1.6.0-rc1.tar.gz
tar -xvzf libnfc-1.6.0-rc1.tar.gz
cd libnfc-1.6.0-rc1 /
svn checkout http: // libnfc.googlecode.com / svn / tags / libnfc-1.6.0-rc1 / debian
I recommend removing debug messages. To do this, in the
debian / rules file, remove the
--enable-debug key in the
dh_auto_configure line.
We collect packages:
dpkg-buildpackage -b -us -uc
Install dependencies and packages:
sudo apt-get install libusb- 0.1 - 4 libpcsclite1 libccid pcscd
sudo dpkg -i .. / libnfc * .deb
To check the reader must be connected and next to it (within its radius) to lie card. We check the performance with the nfc-list command, which displays a list of readers and maps, in the reader field:
$ nfc-list
nfc-list uses libnfc 1.6.0-rc1 (rexported)
NFC device: SCM Micro / SCL3711-NFC & RW - PN533 v2.7 (0x07) opened
ATQA (SENS_RES): 00 04
UID (NFCID1): 5b b8 5f 28
SAK (SEL_RES): 08
If you saw something like this - it means everything works. I first gave the error "
libnfc.driver.pn53x_usb Unable to set USB configuration (Device or resource busy) ". The fact is that in Ubuntu 12.04, some drivers for the PN533 chip are installed by default, and libnfc cannot access the device. It is treated by disabling the built-in driver "
sudo modprobe -r pn533 ".
Mfoc utility
We will use the mfoc utility from the latest version of nfc-tools from the repository. Problems should not arise:
svn checkout http: // nfc-tools.googlecode.com / svn / trunk / mfoc / mfoc
cd mfoc
dpkg-buildpackage -b -us -uc
sudo dpkg -i mfoc_0.10.2pre3.1- 0 _amd64.deb
Looking inside
Theoretical part
MIFARE Classic 1K cards have 1 KB of memory, which is divided into 16 sectors. Each sector consists of 4 blocks of 16 bytes. Each sector is protected by two 48-bit keys A and B (which are stored in 4 blocks).

For an operation with a specific sector, the reader must authenticate with one of the keys (A or B) for that sector. Independent read and write permissions can be assigned to each key. The vulnerability that mfoc uses is that if the key is known to at least one sector, then after authorization for this sector, mfoc tries to authenticate for another sector, and this attempt reveals 32 bits of the key for the new sector. You can read more and correctly in the article
Wirelessly Pickpocketing a Mifare Classic Card .
Practical part
We get the keys to the card using the mfoc utility:
mfoc -O keys.mfd
Somewhere in a minute, you will get the answer "
Auth with all sectors succeeded, dumping keys to a file! ". After that, the keys to the map are in the
keys.mfd file. The keys for all the cards are the same
, and by the way, they have been online for quite some time (obviously, by one of the employees) .
To analyze merge dump card:
nfc-mfclassic r a new00-04-11.mfd keys.mfd
We repeat the procedure after each trip to the metro or refill, and we get a sufficient set of dumps for analysis.
Analysis
I will not give long reflections, I will focus on conclusions. After each operation, two sections of memory are changed: header and history operation.
The card header is two identical blocks at addresses 0x2D0 and 0x2E0. Each block contains information about the serial number of the operation with the card, the remaining number of trips and the date and time of the last operation. The date and time are recorded strangely: bit by bit, and for some reason, seconds are divided by 2 (see code below).
The blocks containing the history of the last six operations are located at addresses 0xC0, 0xD0, 0xE0, 0x100, 0x110, 0x120. Each entry contains:
- date and time of operation,
- the number of the terminal that made the record,
- terminal log entry number
- number of trips.
The terminal number should depend on the metro station, but I'm not completely sure. Date and time are recorded in a different, but the same wonderful format, as in the title.
Example
Python program that reads data from the nfc-mfclassic dump:
#! / usr / bin / env python
import sys
from struct import unpack
from datetime import datetime
def get_crc ( block ) :
"" "XOR all bytes in block" ""
return reduce ( lambda x, y: x ^ ord ( y ) , block, 0 )
def get_bits ( i, s, l ) :
"" "Get l bits starting at s" ""
mask = ( 1 << ( l ) ) - 1
return int ( ( i >> s ) & mask )
def print_info ( data ) :
# card number
number = unpack ( '<4H' , data [ 0x46: 0x4E ] )
print "Card #: tt {3: 04X} {2: 04X} {1: 04X} {0: 04X}" . format ( * number )
# card header blocks
header_block = data [ 0x2D0: 0x2E0 ]
crc_2d = get_crc ( header_block )
crc_2e = get_crc ( data [ 0x2E0: 0x2F0 ] )
print "nHeader: t" + ( "ok" if crc_2d == crc_2e else "err" )
print "Block 0x2D CRC: t {: # x}" . format ( crc_2d )
print "Block 0x2E CRC: t {: # x}" . format ( crc_2e )
# last activity
activity_number, activity2, activity1 = unpack ( '> HLH' , data [ 0x2E2: 0x2EA ] )
activity = activity1 + ( activity2 << 16 )
activity_count = get_bits ( activity, 38 , 10 )
y = get_bits ( activity, 6 , 5 ) + 2000
M = get_bits ( activity, 11 , 4 )
d = get_bits ( activity, 15 , 5 )
h = get_bits ( activity, 20 , 5 )
m = get_bits ( activity, 25 , 6 )
s = get_bits ( activity, 31 , 5 ) * 2
activity_date = datetime ( y, M, d, h, m, s )
activity_position, = unpack ( '> H' , data [ 0x147: 0x149 ] )
activity_index = ( activity_position / 0x40 ) - 32
print "nLast activity #t {}" . format ( activity_number )
print "Positiont {: # x} ({})" . format ( activity_position, activity_index )
print "Date: tt" + activity_date. isoformat ( )
print "Counter: t {}" . format ( activity_count )
# last activities positions
positions = [ 0xC0, 0xD0, 0xE0, 0x100, 0x110, 0x120 ]
print "n {: ^ 20} {: ^ 5} {: ^ 20} {: ^ 12}" . format ( "Date" , "unk" , "Terminal" , "Operation" )
print "{:> 31} {:> 5} {:> 4} {:> 10} {:> 7}" . format ( "ID" , "Type" , "Cnt" , "Type" , "Cnt" )
# get correct order
i = 5 if activity_index > 5 else activity_index
positions_ordered = positions [ ( i + 1 ) : ] + positions [ : ( i + 1 ) ]
for pos in positions_ordered:
block = data [ pos: pos + 0x10 ]
# if undefined or empty block
if ( get_crc ( block ) <> 0 ) or ( ord ( block [ 0 ] ) == 0 ) :
continue
date_i, unk = unpack ( '> LH' , block [ 1 : 7 ] )
term_id, term_type, term_cnt = unpack ( '> BBH' , block [ 7 : 11 ] )
op_type, op_cnt_i = unpack ( '> HH' , block [ 11 : 15 ] )
op_cnt = op_cnt_i / 0x40
h = get_bits ( date_i, 2 , 5 )
m = get_bits ( date_i, 7 , 6 )
s = get_bits ( date_i, 13 , 5 ) * 2
y = 2000 + get_bits ( date_i, 18 , 5 )
M = get_bits ( date_i, 23 , 4 )
d = get_bits ( date_i, 27 , 5 )
date = datetime ( y, m, d, h, m, s )
print "{} {:> # 5x} {:> # 7x} {:> # 5x} {:> # 7x} {:> # 9x} {:> 4}
" . format ( date. isoformat ( ) , unk, term_id, term_type, term_cnt, op_type, op_cnt )
def main ( filename ) :
with open ( filename, "rb" ) as f:
data = f. read ( 1024 )
print_info ( data )
if __name__ == "__main__" :
main ( sys . argv [ 1 ] )
Example of the program:

How can I use
A record of the last 6 trips can be used to view movements. Even if the station is not visible, then you can see the exact date and time of the trip.
On the other hand, you can collect metro statistics. So, as each turnstile records its transaction number on the card, you can see how many people passed through the turnstile. For example, on the dump you can see from the top that approximately 1000 people passed through the turnstile at the Polytechnic Institute (Terminal Number 0x14) per day.