--- Title: Reversing a coffee machine key Summary: At $DAYJOB, a long time ago, we had big a coffee machine allowing us to store money in NFC keys… --- 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.