Hi, i've digged into that random generator function i found.
The full code of SEED generation
Now to the main random-generator. It calculates a 3 byte random sequence (i named the first byte of the memorylocation "randomSeedSequence") used for the challenge send to the tester in an UDS-SecurityAccess flow. Again the disassembled code first (i've added line-numbers in front of the commands, removed nullsubs and changed the names of the loc_... into some more readable ones):
Code: Select all
generateRandomSeed:
1 PUSH {R4-R6,LR}
2 LDR R5, =randomSeedSequence ; pointer to the first of 3 byte sequence
3 MOVS R4, #0
loop1_31BA:
4 BL mscan_getTimerValue ; function returns a 16-bit random value in R0
5 MOVS R1, #0
loop2_31C0:
6 LSLS R3, R0, #1
7 ADDS R0, R3, R0
8 ADDS R0, #29
9 LSLS R0, R0, #16
10 LSRS R0, R0, #16
11 ADDS R1, #1
12 LSLS R1, R1, #24
13 LSRS R1, R1, #24
14 CMP R1, #5
15 BCC loop2_31C0
16 LSRS R1, R0, #8
17 STRB R1, [R5,#2]
18 STRB R0, [R5,#1]
19 BL mscan_getTimerValue ; function returns a 16-bit random value in R0
20 MOVS R1, #0
loop3_31E4:
21 MOVS R3, #29
22 MULS R0, R3
23 ADDS R0, #3
24 LSLS R0, R0, #16
25 LSRS R0, R0, #16
26 ADDS R1, #1
27 LSLS R1, R1, #24
28 LSRS R1, R1, #24
29 CMP R1, #5
30 BCC loop3_31E4
31 LDRB R1, [R5,#1]
32 LSRS R2, R0, #8
33 EORS R1, R2
34 STRB R1, [R5,#1]
35 LSLS R0, R0, #24
36 LSRS R0, R0, #24
37 STRB R0, [R5]
38 ADDS R4, #1
39 LSLS R4, R4, #24
40 LSRS R4, R4, #24
41 CMP R0, #0
42 BNE generateRandomSeed_exit
43 CMP R1, #0
44 BNE generateRandomSeed_exit
45 LDRB R0, [R5,#2]
46 MOVS R1, R5
47 CMP R0, #0
48 BNE generateRandomSeed_exit
49 CMP R4, #5
50 BCC loc_322C
51 MOVS R0, #3
52 STRB R0, [R1]
53 MOVS R0, #0x1D
54 STRB R0, [R1,#1]
55 MOVS R0, #0x65
56 STRB R0, [R1,#2]
loc_322C:
57 CMP R4, #5
58 BCC loop1_31BA
generateRandomSeed_exit:
59 POP {R4-R6}
60 POP {R3}
61 BX R3
Generating random values
The "generateRandomSeed" function needs to get a random number. The firmware provides this by reading the current value of the FlexCAN-D-Port timer. By the datasheet this is a freerunning 16-bit timer, clocked by the configured baudrate speed of the CAN bus. Means, everytime one read the timer-register 0xFC0A000A he get's a random 16-bit value in the lower half-word of the 32-bit register.
The ARM code of this function is:
Code: Select all
mscan_getTimerValue:
LDR R0, =FC0A0000 ; FlexCAN D Base Register (datasheet 23.5.2.1)
LDRH R0, [R0,#0x0A] ; Read 0xFC0A000A = TIMER (datasheet 23.5.2.3)
BX LR ; R0 = 0x0000TTTT
It could be translated to "c" like this:
Code: Select all
uint16_t mscan_getTimerValue()
{
uint32_t *ptr = 0xFC0A000A;
return &ptr & 0xFFFF;
}
Now let's analyze what this code is doing
In line 2, register R5 is set with a pointer to the start of the randomSeedSequence. This is what the function does as a side-effect.
The first crypto-loop
Code: Select all
4 BL mscan_getTimerValue ; function returns a 16-bit random value in R0
05 MOVS R1, #0 ; init loop counter with 0
loop2_31C0:
06 LSLS R3, R0, #1 ; R3 = R0 * 2
07 ADDS R0, R3, R0 ; R0 = R0 + R3
08 ADDS R0, #29 ; R0 = R0 + 29
09 LSLS R0, R0, #16
10 LSRS R0, R0, #16
; increase loop counter by 1
11 ADDS R1, #1
12 LSLS R1, R1, #24
13 LSRS R1, R1, #24
; compare loop counter is below 5
14 CMP R1, #5
15 BCC loop2_31C0
The first loop in line 5-15 take the 16-bit random number in R0 (delivered from the timervalue subfunction called in line 4) and do some calculation with it.
R1 is a loop counter (set to 0 in line 5) which get's increased in line 11. Line 12+13 looks odd but in fact they just ensure the counter value stays at 8 bit. Shifting a value in a 32-bit register by 24 bits to the left and back to the right again is nothing else than setting all higher bits to zero. It's like in C a " <value> & 0xFF".
The loop itself should only iterate 5 times. As the loop counter is initialized with 0, it should break if counter reaches value 5. This is done with CMP in line 14 and BCC (
Branch if
Carry is
Clear) in line 15. The CMP compares the loop counter value in R1 with 5. This sets or clears some flags in the processor. It sound strange but on ARM the Zero-Flag AND the Carry-Flag is set when two values match. The BCC only jumps to the loop address if the carry-flag is not set, so if the counter reaches 5 it will passed by.
In C this would look like:
Code: Select all
for (uint8_t loop1=0; loop1<5; loop1++) { ... }
Lines 6-10 calculates the timer value. Line 6 shift the value 1 bit to the left, which is mathematically the same als multiply it by two.
The result is added to the timer value in line 7. Line 8 adds a fixed value of 29 to the result. And finally line 9+10 do the left-shift/right-shift shuffle to mask out the higher 24 bits so that only a 8-bit value remains.
In C this is:
Code: Select all
uint16_t rnd;
rnd = mscan_getTimerValue();
...
rnd = ((rnd * 2) + rnd + 29) & 0xFF;
Lines 16-18 store the so calculated value to the sequence pointed by R5. Line 16 will mask the high-byte of the random-word and stores it at randomSeedSequence[2], whereas line 18 stores the low-byte of the random-word into randomSeedSequence[1].
So the full loop code above would be:
Code: Select all
uint16_t rnd;
rnd = mscan_getTimerValue();
for (uint8_t loop1=0; loop1<5; loop1++) {
rnd = ((rnd * 2) + rnd + 29) & 0xFFFF;
}
randomSeedSequence[1] = rnd & 0xFF;
randomSeedSequence[2] = (rnd >> 8) & 0xFF;
the second crypto-loop
The loop in lines 19-30 does exact the same thing as in the first loop, only the calculation and target bytes differ. It fetches a random word from the timer and iterates 5 times over this calculation in line 21-25:
Code: Select all
21 MOVS R3, #29
22 MULS R0, R3
23 ADDS R0, #3
24 LSLS R0, R0, #16
25 LSRS R0, R0, #16
Line 21+22 multiplies the random value by "29", line 23 adds "3" on the result and line 24+25 masks out the high-word bits so only a 16-bit value remain.
The rest of the code there is to calculate a little more with the former results:
Code: Select all
31 LDRB R1, [R5,#1]
32 LSRS R2, R0, #8
33 EORS R1, R2
34 STRB R1, [R5,#1]
35 LSLS R0, R0, #24
36 LSRS R0, R0, #24
37 STRB R0, [R5]
In 31 it loads the second byte (index #1) of the randomSeedSequence into R1.
Line 32 loads R2 with the high-byte of the timer-word.
Line 33 makes an XOR of both values (this is a disassembler short for "EORS R1, R2, R1", means R1 = R1 XOR R2).
In line 34 it stores the value back into the sequence.
Line 35+36 masks out the low-byte of the timer-word and stores it in line 37 as the first byte of the sequence.
In C that would be:
Code: Select all
rnd = mscan_getTimerValue();
for (uint8_t loop2=0; loop2<5; loop2++) {
rnd = ((rnd * 29) + 3) & 0xFFFF;
}
randomSeedSequence[0] = rnd & 0xFF;
randomSeedSequence[1] = randomSeedSequence[1] ^ (rnd >> 8);
The exceptions
What now follows in the code can be described as safety and fallback methods, which ensures that there is a valid SEED sequence generated and the calling code can rely on that without error checking.
Under normal circumstances the work is done now, but for safety reasons the developers wanted to ensure that the outcome does not contain three zero bytes (0x00 0x00 0x00) as a SEED sequence. Because of the random values and various calculations it is very unlikely for this to happen - but in case of this they simply start over the whole process again. This is done for 5 times and for the un-un-un-unlikely scenario that all those iterations result in three null bytes, there is a fixed fallback sequence 0x00, 0x1D, 0x65 build in.
This is how it looks in disassembler:
Code: Select all
31 LDRB R1, [R5,#1]
...
37 STRB R0, [R5]
38 ADDS R4, #1
39 LSLS R4, R4, #24
40 LSRS R4, R4, #24
41 CMP R0, #0
42 BNE generateRandomSeed_exit
Increase the loop-counter value. Then check if R0 (contains the high-byte of the last generated random word) is zero. If not, we are done and can exit and return. This means at least 2 bytes must not be zero, one could be zero in the resulting sequence.
This also follows for the low-byte of the random word:
Code: Select all
43 CMP R1, #0
44 BNE generateRandomSeed_exit
And even if the first two bytes of the random sequence where 0x00, we can also return with it as long as the last byte is something else:
Code: Select all
45 LDRB R0, [R5,#2]
46 MOVS R1, R5 ; <- this is not relevant for the branch.
47 CMP R0, #0
48 BNE generateRandomSeed_exit
If we haven't break-off the loop above, the code generated an null-byte sequence. If that happens for 5 times, then use a fixed default sequence instead:
Code: Select all
49 CMP R4, #5
50 BCC loc_322C
51 MOVS R0, #3
52 STRB R0, [R1]
53 MOVS R0, #0x1D
54 STRB R0, [R1,#1]
55 MOVS R0, #0x65
56 STRB R0, [R1,#2]
loc_322C:
...
At least ensure we only iterate 5 times:
Code: Select all
loc_322C:
57 CMP R4, #5
58 BCC loop1_31BA
This can be put in C as:
Code: Select all
// try to generate a valid 3 byte random seed (valid means that there must be at least one non-zero byte in it)
for (uint8_t n=0; n<5; n++)
{
...
if (randomSeedSequence[0] || randomSeedSequence[1] || randomSeedSequence[2]) {
break;
}
}
// fallback to a default value of no sequence could be generated
if ( ! randomSeedSequence[0] && ! randomSeedSequence[1] && ! randomSeedSequence[2]) {
randomSeedSequence = { 0x03, 0x1D, 0x65 };
}
Putting it all together...
The full code of generating a seed in C looks like this:
Code: Select all
/**
* Create a sequence of 3 random bytes
*/
void createRandomSeed(uint8_t *seed)
{
uint16_t rnd;
// try to generate a valid 3 byte random seed for max. 5 times
for (uint8_t n=0; n<5; n++)
{
// first calculation
rnd = mscan_getTimerValue();
for (uint8_t i=0; i<5; i++) {
rnd = ((rnd * 2) + rnd + 29) & 0xFFFF;
}
seed[1] = rnd & 0xFF;
seed[2] = (rnd >> 8) & 0xFF;
// second calculation
rnd = mscan_getTimerValue();
for (uint8_t i=0; i<5; i++) {
rnd = ((rnd * 29) + 3) & 0xFFFF;
}
seed[0] = rnd & 0xFF;
seed[1] = seed[1] ^ (rnd >> 8);
// if there is at least one non-zero byte in the sequence, where done!
if (seed[0] || seed[1] || seed[2]) {
break;
}
}
// fallback to a default value of no sequence could be generated
if ( ! seed[0] && ! seed[1] && ! seed[2]) {
seed[0] = 0x03;
seed[1] = 0x1D;
seed[2] = 0x65;
}
}
You can test this code by creating a C source this this, appended with the function above:
Code: Select all
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
uint16_t timerValue();
void createRandomSeed(uint8_t *);
int main()
{
uint8_t seed[3];
time_t t;
// init random generator
srand((unsigned) time(&t));
// create the seed bytes
createRandomSeed(seed);
printf("Random SEED: %02x %02x %02x\n", seed[0], seed[1], seed[2]);
return 0;
}
/**
* Return a 16 bit random value
*/
uint16_t mscan_getTimerValue()
{
//uint32_t *ptr = 0xFC0A000A;
//return &ptr & 0xFFFF;
return rand() % 0x10000; // simulation of FlexCAN timer
}
It will run an give you a nice 3 byte random sequence every time.
Conclusions
Now, this is not thaaat advanced but it shows how reverse engineering can be done. It also shows us how the firmware is doing it and that it really creates a RANDOM seed, not seeds of a fixed sequence out of an array. This way it is also clear that the security access function does not care which are the random bytes, nor how they where created. To in fact you could choose any other way to simulate that. An so we could also patch the firmware to simply use the same random seed sequence every time