Proxmark3 community

Research, development and trades concerning the powerful Proxmark3 device.

Remember; sharing is caring. Bring something back to the community.

"Learn the tools of the trade the hard way." +Fravia

You are not logged in.


Time changes and with it the technology
Proxmark3 @ discord

Users of this forum, please be aware that information stored on this site is not private.

#1 2019-10-14 13:45:25

From: Leipzig/Germany
Registered: 2019-10-11
Posts: 3

Identify data on Legic Prime

Hello Everyone!

First, I want to thank Iceman, mosci, jason, and ikarus, for all the time and work they've invested in Proxmark firmware and scripts.
I've learned so much new in the last few weeks and months.

I've got some LegicPrime-Tags used for a car park and try to understand all data stored on this tags.

After much research I know almost all bytes, CRC calculations and how they are used, but not all.
(Sources: … 11-03_.pdf, … slides.pdf, Mosci's Legic-Lua-Scripts and legic-threads of this forum)

Here's a Dump of a Tag:

[usb] pm3 --> hf legic info
[+] Reading full tag memory of 256 bytes...          

CDF: System Area           
MCD: 79, MSN: 03 ee 7e, MCC: df OK           
DCF: 60000 (60 ea), Token Type = IM-S (OLE = 0)          
WRP = 15, WRC = 1, RD = 1, SSC = FF          
Remaining Header Area          
00 00 00 11 01 06 80 00 00 4C 50 00 00           

ADF: User Area           
Segment     | 01           
raw header  | 0x74 0xC0 0x07 0x40          
Segment len | 116,  Flag: 0xC (valid:1, last:1)          
            | WRP: 07, WRC: 04, RD: 0, CRC: 0x85 (OK )          

WRC protected area:   (I 27 | K 0| WRC 4)          

row  | data          
[00] | 50 38 0B 00 
Remaining write protected area:  (I 31 | K 31 | WRC 4 | WRP 7  WRP_LEN 3)          

row  | data          
[00] | 03 B3 22 
Remaining segment payload:  (I 34 | K 34 | Remain LEN 104)          

row  | data          
[00] | FF 92 00 00 03 B3 22 00 00 00 00 00 00 00 00 00 
[01] | 00 00 00 FF FF FF FF FF FF FF 57 02 85 26 FC 01 
[02] | 01 00 00 64 00 00 00 00 00 85 26 3C 04 F4 01 00 

...and Raw-Data, I like more smile

ADR  |0x0|0x1|0x2|0x3|0x4|0x5|0x6|0x7|0x8|0x9|0xA|0xB|0xC|0xD|0xE|0xF
0x00 | 79  03  EE  7E  DF  60  EA  9F  FF  00  00  00  11  01  06  80
0x10 | 00  00  4C  50  00  00  74  C0  07  40  85  50  38  0B  00  03
0x20 | B3  22  FF  92  00  00  03  B3  22  00  00  00  00  00  00  00
0x30 | 00  00  00  00  00  FF  FF  FF  FF  FF  FF  FF  57  02  85  26
0x40 | FC  01  01  00  00  64  00  00  00  00  00  85  26  3C  04  F4
0x50 | 01  00  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF
0x60 | ...

What I know atm:

  MCD:    Byte 0x00        => 79
  MSN:    Byte 0x01..0x03      => 03 EE 7E
  UID-CRC: Byte 0x04 = CRC8 over Byte 0x00..0x03  => DF

DecrementalField DCF:
  OLE (Organization Level Enabled-Flag): DCF-Lo-Byte 0x05 Bit 7 => binary 0
  TokenType: DCF-Lo-Byte 0x05 Bits 0..6  => binary 1000000 = 0x60
    (0x00-0x2f = IAM, 0x30-0x6f = SAM, 0x70-0x7f = GAM)
  Organization Level: DCF-Hi-Byte = Byte 0x06  => EA
  0xEA 0x60 => so it's a SAM-Token

Byte 0x07:
    WRP (WriteProtection): Bit 0..3    => binary 1111 = decimal 15 = 0xF
    WRC (WriteControll): Bit 4..6     => binary 001  = decimal 1  = 0x1
    RD (Read Disabled): Bit 7      => binary 0    = decimal 1  = 0x1

Bytes 0x08..0x0C are unkown, but important???

Bytes 0x0D..0x13:
    According to … 11-03_.pdf i was able to identify how to calculate the CRC on Byte 0x13. The CRC-Calculation must be done with DIRT-Flag-Set:
    So CRC-Calcutlation is done over:
    [MCD][MSN0][MSN1][MSN2][MSN3] + Byte 0x0D but BIT7 (=DIRT-Bit) MUST SET FOR CALCULATION = 8, BIT 0-6 is Seg-Nr  (max Seg = 127) + Bytes 0x0E..0x12

			[offline] pm3 --> hf legic crc d 7903ee7e81068000004c
			[+] Legic crc8: 50  <=== Byte 0x13

Bytes 0x14..0x15:
  Timestamp (Week/Year) for Token but not used on SAM-Tokens

Bytes 0x16..0x1A:
  Segment Header with CRC:
  CRC calculated over [MCD][MSN0][MSN1][MSN2][MSN3]+0x16..0x19

		[offline] pm3 --> hf legic crc d 7903ee7e74c00740
		[+] Legic crc8: 85  <=== Byte 0x1A

  Segment Header contains following informations:
    Segment-length (including Header):
      Byte 0x16 = Lower Byte of SegLength => 0x74
      Byte 0x17 Bits 0..3 = High Nibble of SegLenght => 0x0
      so SegmentLeght = 0x074 = 116 Byte
    Valid-Flag (if not set, segment has been deleted):
      Byte 0x17 Bit 6 => 1
    Last-Segment-Flag (if bit is set, no more segments are following):
      Byte 0x17 Bit 7 => 1   
    WRP (WriteProtection, amount of bytes of WriteProtected Area???):
      Byte 0x18 => 0x07
    WRC (WriteControl, amount of Bytes of WriteControlled Area???):
      Byte 0x19 Bits 4..6 => Byte 0x19 => 0x40 = binary 010000000 so WRC = 100 = 4
    RD (ReadDisabled):
      Byte 0x19 Bit 7 => 0

So far, all these bytes are known and have been discussed several times in the forum.
Now the fun-stuff:

Bytes 0x1B..0x1E:
  WRC-controled area = Stamp

Bytes 0x1F..0x21:
  Remaining Bytes of WRP-protected area.
  I figured out that byte 0x21 is the LowerByte and 0x20 is HigherByte of cardnumber printend on this tag.
  0x22B3 = decimal 8883 
  Printed Number on Tag: 8883 
  Byte 0x1F may could also belong to this cardnumber...

  I'm sure that these bytes contain the card number, since on another card the bytes contains exactly the printed number.

So i think it isn't a default Kaba-Header, cause there's no more space left in WRP-Area for the khCRC.

And now the really interesting stuff, the payload:

Byte 0x22    0xFF on all tags
Byte 0x23    different on other tags, maybe a CRC, but i can't figure out which bytes are included in calculation
Bytes 0x24..0x25  0x00 on all Ttags
Bytes 0x26..0x28  are the same value as bytes 0x1F..0x21 on every tag. It's the card number again.
Bytes 0x29..0x34  0x00 on all tags
Bytes 0x35..0x3B  0xFF on all tags

All Bytes before 0x3A are never changed when tag was used.

Byte 0x3C    unknown, changed every time the tag is used randomly(?)
Byte 0x3D    unknown, but i think it's the number/type of reader, last used to change data on tag. If I used the Cash-Upload-Terminal, this Byte is set to 0x03, if I entered the car park, it was set to 0x01 and when i've leaved, it was set to 0x02.
Byte 0x3E    unknown, but it's icremented every day. On 2019-10-04 it was 0x7e, on 2019-10-09 => 0x83, 2019-10-10 => 0x84, 2019-10-11 => 0x85
Byte 0x3F    unknown, maybe it belongs to byte 0x3F and is a part of the "timestamp"

I think these are the number of days since 1992-10-11. It's calculated by byte 0x3F as hiByte and 0x3E as lobyte. (Why the 1992-10-11? Legic introduced the Legic Prime Standard in 1992)

Bytes 0x40..0x41  Minutes of Day, HiByte = 0x41, LoByte = 0x40 => 0x01FC = 508/60 = 8.46 => 0.46*60 ~ 27.99 => entering car park at 08:28 , maybe this Bytes are part of the "timestamp"
Byte 0x42      unknown, think it would set if the tag is used the first time entring the car park, because it's 0x00 after initial money-upload and set to 0x01 after first time the car park was entered
Byte 0x43      is set to 0x08 after entering the parking lot, after leaving it is 0x00
Byte 0x44      unkown , maybe it's the HiByte of cash?
Bytes 0x45..0x46  Balance of the cash, HiByte = 0x45 , LoByte = 0x44 => 0x0064 = 100 = 1.00€
Bytes 0x47..0x4A  0x00 on all tags
Bytes 0x4B..0x51  0x00 after initial cash-upload and after entring the car park, so there is NO CRC to check the cash-balance. Beat me if I am wrong, but a checksum must always be behind the values to be tested, right???
          After leaving car park bytes are set to:
          Bytes 0x4B..0x4C:  same structure like bytes 0x3E..0x3F, days since 1992-10-11
          Bytes 0x4D..0x4E:   Minutes of day, same like Bytes 0x40..0x41, but for leaving car park => 0x043c = 1084/60 = 18.0667 => 0.0667*60 ~ 4 => leaving car park at 18:04
          Byte 0x4F:  unknown, 0xF4 on all tags
          Byte 0x50:  unknown, 0x01 on all tags
          Byte 0x51:  0x00 on all tags
...and that's it...

Some Questions about this Tags:
- They do not contain a typical Kaba header, or am I wrong?
- Are these tags a kind of Legic Cash? I do not think so. I suspect it is more of a proprietary development of the provider.
- I have absolutely no idea what byte 0x3C might mean, maybe bytes 0x3C and 0x3D belong together?



#2 2019-10-14 15:58:25

Registered: 2013-04-25
Posts: 9,507

Re: Identify data on Legic Prime

Nice research. 
The user segments on a Legic is multiple and most implementations uses their own. Some was documented in the awesome legic.lua script.


#3 2019-12-09 18:22:02

Registered: 2016-07-21
Posts: 55

Re: Identify data on Legic Prime

electron wrote:

  0xEA 0x60 => so it's a SAM-Token

Thats not right. See the code changes I done a long time ago: DCF must be greater than 60000 to be any kind of Authorisation-Media (IAM, SAM, GAM etc.). That the reason no one could change/revert a IM/IM-S card back to a - lets say - IAM type of card. The DCF field is 60000 for IM/IM-S at minimum and could not be incremented again.

This is a normal type of IM-S media, or lets say: A normal user card.

electron wrote:

- They do not contain a typical Kaba header, or am I wrong?

Right. The KABA header definition (KGH) also includes the definition of a BCD encoded card number in its header with 3 bytes (6 digits). The shown header couln't be a KGH. It is a user/integrator defined type of data structure.

electron wrote:

- Are these tags a kind of Legic Cash? I do not think so. I suspect it is more of a proprietary development of the provider.

No, it isn't. Either the data structure nor the data size fits in any case. This is a data structure defined by the system developer. I never saw such a data structure, so I don't know who developed this. I also don't know the owner of the 0x50 Legic license. I also newer saw this before. Maybe this is sub-licensed...

electron wrote:

- I have absolutely no idea what byte 0x3C might mean, maybe bytes 0x3C and 0x3D belong together?

Most likely this is some kind of checksum. A checksum, a question you asked before, does not have to be on the end of "something". It could be placed everywhere. Also this does not have to be generated by the Legic reader module API itself. You could write whatever you want. Sometimes I prefer to place checksums in front of data blocks, especially if any kind of encryption is envolved. This "randomizes" the data stream itself in a very handy way. So minior changes to the data do result in massive changes in the data block, no matter what kind of encryption is used.

Over all the data is constructed in a strange way. Especially the fact the card amount seems to be unique in card. This is some how stupid, because data corruption blows up everything. There should be a kind of paging/mirroring.

The best way to check if there's any checksum involved is to save the current content of the card, modifying the credit amount to somewhat else and check if this system still accepts the card (if not restore the saved data).
Maybe the "randomly" changing byte is a kind of "rolling code", to ensure the card content could not be reverted to an earlier value. Maybe all the stored data is just a "fail save" storage, because the system continuesly stores all data online and use the card data only in "offline state" as a fail-save mode. In most cases card parking systems act this way.

As this looks like a very bad design, I don't think this is "save" in any way. The only show stopper might be the fact the data on card is just a fail-save storagem and normally the systems always stores everything "online".

Last edited by Jason (2019-12-09 18:24:38)


#4 2019-12-19 11:46:53

From: Leipzig/Germany
Registered: 2019-10-11
Posts: 3

Re: Identify data on Legic Prime

Thx @Jason for the answers/informations!

I've tested a little more ...
Apparently only the data of the card are used and no "online" data.
If the date or credit amount on the card is changed, this data is also used by the control system.
So it's a very stupid and insecure system ...


Board footer

Powered by FluxBB