2021-05-07 13:35:46 +00:00
|
|
|
---
|
2021-05-17 21:37:15 +00:00
|
|
|
Title: Reversing a coffee machine key
|
2021-05-31 11:35:18 +00:00
|
|
|
Summary: At $DAYJOB, a long time ago, we had big a coffee machine allowing us to store money in NFC keys…
|
2021-05-07 13:35:46 +00:00
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
At $DAYJOB, a long time ago, we had big a coffee machine allowing us
|
|
|
|
to store money in NFC keys. NFC keys were Mifare 1K ones, so they had
|
|
|
|
a security hole (Search mfoc), so I tried reverse engineering them,
|
|
|
|
you know, free coffee…
|
|
|
|
|
|
|
|
Before starting you can [download key
|
|
|
|
dumps](https://mdk.fr/x/keys.tar.bz2) to follow along with me.
|
|
|
|
|
|
|
|
I will not paste all dumps in this page, (1k dumps are big in hexadecimal on a
|
|
|
|
blog post) but I dumped a few keys with a few different values, and
|
|
|
|
I'll post the diffs between dumps.
|
|
|
|
|
|
|
|
I got two keys, and two dumps per key. First key from 9.2€ to
|
|
|
|
8.35€. Second key from 0€ to 0.10€
|
|
|
|
|
|
|
|
Diff of the first key (Between a dump of 9.2€ and a dump of 8.35€):
|
|
|
|
|
|
|
|
```diff
|
|
|
|
< 2 060 b491 7e19 0000 0000 0000 0000 1801 003f 100 R:AB W:-B I:-- DTR:-- r/w block
|
|
|
|
---
|
|
|
|
> 2 060 82bb 261a 0000 0000 0000 0000 1c01 0020 100 R:AB W:-B I:-- DTR:-- r/w block
|
|
|
|
26,27c26,27
|
|
|
|
< 0 080 9803 0000 67fc ffff 9803 0000 09f6 09f6 110 R:AB W:-B I:-B DTR:AB r/w block
|
|
|
|
< 1 090 c503 0000 3afc ffff c503 0000 09f6 09f6 110 R:AB W:-B I:-B DTR:AB r/w block
|
|
|
|
---
|
|
|
|
> 0 080 4303 0000 bcfc ffff 4303 0000 09f6 09f6 110 R:AB W:-B I:-B DTR:AB r/w block
|
|
|
|
> 1 090 7003 0000 8ffc ffff 7003 0000 09f6 09f6 110 R:AB W:-B I:-B DTR:AB r/w block
|
|
|
|
```
|
|
|
|
|
|
|
|
Diff of the second key (Between a dump of 0€ and a dump of 0.10€):
|
|
|
|
|
|
|
|
```diff
|
|
|
|
< 2 060 daa0 9019 0000 0000 0000 0000 2200 0098 100 R:AB W:-B I:-- DTR:-- r/w block
|
|
|
|
---
|
|
|
|
> 2 060 2eaf 261a 0000 0000 0000 0000 2300 00d2 100 R:AB W:-B I:-- DTR:-- r/w block
|
|
|
|
26,27c26,27
|
|
|
|
< 0 080 0000 0000 ffff ffff 0000 0000 09f6 09f6 110 R:AB W:-B I:-B DTR:AB r/w block
|
|
|
|
< 1 090 2d00 0000 d2ff ffff 2d00 0000 09f6 09f6 110 R:AB W:-B I:-B DTR:AB r/w block
|
|
|
|
---
|
|
|
|
> 0 080 0a00 0000 f5ff ffff 0a00 0000 09f6 09f6 110 R:AB W:-B I:-B DTR:AB r/w block
|
|
|
|
> 1 090 0000 0000 ffff ffff 0000 0000 09f6 09f6 110 R:AB W:-B I:-B DTR:AB r/w block
|
|
|
|
```
|
|
|
|
|
|
|
|
To start let's focus on the two-lines diff, at adresses `0x080` and `0x090`.
|
|
|
|
|
|
|
|
When I reverse engineer I like to loop between "presentation" (put an
|
|
|
|
effort to make the data readable) and "understanding" (get an
|
|
|
|
information from the data), so my first step, is to render this in a
|
|
|
|
clean way. I had an intuition for a one's complement (as I spotted
|
|
|
|
`ffff` / `0000`, what an intuition...), so I wanted to see binary
|
|
|
|
data. I also dropped columns of data that were identical between two
|
|
|
|
dumps:
|
|
|
|
|
|
|
|
```text
|
|
|
|
9.2 : 9803 0000 67fc c503 0000 3afc | 10011000.00000011 ... 01100111.11111100 11000101.00000011 ... 00111010.11111100
|
|
|
|
8.3 : 4303 0000 bcfc 7003 0000 8ffc | 01000011.00000011 ... 10111100.11111100 01110000.00000011 ... 10001111.11111100
|
|
|
|
0.1 : 0a00 0000 f5ff 0000 0000 ffff | 00001010.00000000 ... 11110101.11111111 00000000.00000000 ... 11111111.11111111
|
|
|
|
0.0 : 0000 0000 ffff 2d00 0000 d2ff | 00000000.00000000 ... 11111111.11111111 00101101.00000000 ... 11010010.11111111
|
|
|
|
```
|
|
|
|
|
|
|
|
So, my first intuition was true: the data is stored twice, the 2nd one
|
|
|
|
is the one's complement of the first. So half of the data is useless
|
|
|
|
for me, I can drop it from my representation.
|
|
|
|
|
|
|
|
Follow a simplified presentation witout duplicate (complemented) data:
|
|
|
|
|
|
|
|
```text
|
|
|
|
9.2 : 9803 c503 10011000.00000011 11000101.00000011 0398 -> 920 | 03c5 -> 965
|
|
|
|
8.3 : 4303 7003 01000011.00000011 01110000.00000011 0343 -> 835 | 0370 -> 880
|
|
|
|
0.1 : 0a00 0000 00001010.00000000 00000000.00000000 000A -> 10 | 0000 -> 0
|
|
|
|
0.0 : 0000 2d00 00000000.00000000 00101101.00000000 0000 -> 0 | 002d -> 45
|
|
|
|
```
|
|
|
|
|
|
|
|
At this point I see `0a00` on the `0.10€` key, as `0a(16)` is
|
|
|
|
`10(10)`, `0a00` is `10` in big endian... money may be stored here…
|
|
|
|
in big endian in 1/100 of euros. Let's test with `9803(16be)`, gives
|
|
|
|
`920(10)` that give `9.20€`, yes!! Free coffee not far away!
|
|
|
|
|
|
|
|
This is the big part of the dump, the remaining part (top one) seems
|
|
|
|
to store metadata but is not reversed yet.
|
|
|
|
|
|
|
|
Follow two tables, for the two keys, showing old_value -> new_value,
|
|
|
|
with, for each value, its binay representation and its base 10
|
|
|
|
representation as if value is stored in big endian.
|
|
|
|
|
|
|
|
In the following table, 16be mean "From base 16 big endian to
|
|
|
|
decimal", 16le for little endian.
|
|
|
|
|
|
|
|
```text
|
|
|
|
9.2 -> 8.35
|
|
|
|
Value: As binary 16be 16le Value: As binary 16be 16le
|
|
|
|
DAA0 : 11011010.10100000 41178 55968 -> 2EAF : 00101110.10101111 44846 11951 date ?
|
|
|
|
9019 : 10010000.00011001 6544 36889 -> 261A : 00100110.00011010 6694 9754
|
|
|
|
2200 : 00100010.00000000 34 8704 -> 2300 : 00100011.00000000 35 8960 count ?
|
|
|
|
0098 : 00000000.10011000 38912 152 -> 00D2 : 00000000.11010010 53760 210
|
|
|
|
```
|
|
|
|
|
|
|
|
```text
|
|
|
|
0.0 -> 0.1
|
|
|
|
Value: As binary 16be 16le Value: As binary 16be 16le
|
|
|
|
B491 : 10110100.10010001 37300 46225 -> 82BB : 10000010.10111011 48002 d33467 date ?
|
|
|
|
7E19 : 01111110.00011001 6526 32281 -> 261A : 00100110.00011010 6694 9754
|
|
|
|
1801 : 00011000.00000001 280 6145 -> 1C01 : 00011100.00000001 284 7169 count ?
|
|
|
|
003F : 00000000.00111111 16128 63 -> 0020 : 00000000.00100000 8192 32
|
|
|
|
```
|
|
|
|
|
|
|
|
Non reversed data:
|
|
|
|
|
|
|
|
```text
|
|
|
|
mandark@blanc$ grep 00000060 *.dmp.hex | column -t
|
|
|
|
step1-0.dmp.hex:00000060 da a0 90 19 00 00 00 00 00 00 00 00 22 00 00 98 |............"...|
|
|
|
|
step2-0.1.dmp.hex:00000060 2e af 26 1a 00 00 00 00 00 00 00 00 23 00 00 d2 |..&.........#...|
|
|
|
|
step3-0.2.dmp.hex:00000060 53 1a 51 1a 00 00 00 00 00 00 00 00 24 00 00 98 |S.Q.........$...|
|
|
|
|
```
|
|
|
|
|
|
|
|
```text
|
|
|
|
mandark@blanc$ grep 00000060 *.dmp.hex | column -t
|
|
|
|
step1-9.2.dmp.hex:00000060 b4 91 7e 19 00 00 00 00 00 00 00 00 18 01 00 3f |..~............?|
|
|
|
|
step2-8.35.dmp.hex:00000060 82 bb 26 1a 00 00 00 00 00 00 00 00 1c 01 00 20 |..&............ |
|
|
|
|
step3-3.9.dmp.hex:00000060 c7 9a 59 1a 00 00 00 00 00 00 00 00 2a 01 00 91 |..Y.........*...|
|
|
|
|
```
|
|
|
|
|
|
|
|
Clearly `0022 0023 0024`, and `0118 011C 012A` are juste counters. I only
|
|
|
|
add 10 cents on the key1 between each dumps, but I drink some coffee
|
|
|
|
between each dumps on key2, so it's normal values. I now know I drank
|
|
|
|
18 coffees between first and last dump!
|
|
|
|
|
|
|
|
```text
|
|
|
|
-----------------------------------------------------------------------------------
|
|
|
|
|money | counter | Last byte ? | First long, kind of timestamp |
|
|
|
|
|---------------------------------------------------------------------------------|
|
|
|
|
|euro | hex dec | hex dec bin | hex little endian dec |
|
|
|
|
|---------------------------------------------------------------------------------|
|
|
|
|
|0 | 22 00 34 | 98 152 10011000 | da a0 90 19 19 90 a0 da 428908762 |
|
|
|
|
|0.1 | 23 00 35 | D2 210 11010010 | 2e af 26 1a 1a 26 af 2e 438742830 |
|
|
|
|
|0.2 | 24 00 36 | 98 152 10011000 | 53 1a 51 1a 1a 51 1a 53 441522771 |
|
|
|
|
|---------------------------------------------------------------------------------|
|
|
|
|
|9.2 | 18 01 280 | 3F 63 00111111 | b4 91 7e 19 19 7e 91 b4 427725236 |
|
|
|
|
|8.35 | 1C 01 284 | 20 32 00100000 | 82 bb 26 1a 1a 26 bb 82 438745986 |
|
|
|
|
|3.9 | 2A 01 298 | 91 145 10010001 | c7 9a 59 1a 1a 59 9a c7 442079943 |
|
|
|
|
-----------------------------------------------------------------------------------
|
|
|
|
```
|
|
|
|
|
|
|
|
First long seems to be a kind of timestamp, but it's not a unix
|
|
|
|
timestamp. It seems to count seconds, but I don't know the start
|
|
|
|
point. Start point may be random ^-^
|
|
|
|
|
|
|
|
|
|
|
|
About last byte, I tried some crc's (namely crc-8, crc-8-darc,
|
|
|
|
crc-8-i-code, crc-8-itu, crc-8-maxim, crc-8-rohc, crc-8-wcdma, crc-16,
|
|
|
|
crc-16-buypass, crc-16-dds-110, crc-16-dect, crc-16-dnp,
|
|
|
|
crc-16-en-13757, crc-16-genibus, crc-16-maxim, crc-16-mcrf4xx,
|
|
|
|
crc-16-riello, crc-16-t10-dif, crc-16-teledisk, crc-16-usb, x-25,
|
|
|
|
xmodem, modbus, kermit, crc-ccitt-false, crc-aug-ccitt, crc-24,
|
|
|
|
crc-24-flexray-a, crc-24-flexray-b, crc-32, crc-32-bzip2, crc-32c,
|
|
|
|
crc-32d, crc-32-mpeg, posix, crc-32q, jamcrc, xfer, crc-64, crc-64-we,
|
|
|
|
crc-64-jones)
|
|
|
|
|
|
|
|
I tried with last byte set to any possible value and I '% 255'ed
|
|
|
|
results, also tried without last byte, so I got a lot of false
|
|
|
|
positive matches, for example, for step1-0.dmp.hex:00000060, I have 39
|
|
|
|
possibilities yielding to 98, but I found NO possibility working with
|
|
|
|
the same params for two different dumps.
|
|
|
|
|
|
|
|
We may try to compute more value in CRC's, for example whole block, I
|
|
|
|
just tried to CRC a single line (16 bytes), but I stopped my research
|
|
|
|
here and get back to work.
|