Lire/ecrire un EM4170 avec un RDM630+ESP8266

Tags: électronique voiture ID48 EM4070 EM4170 megamos EDM6300

J'ai eu l'occasion de jouer un peu avec des EM4070/EM4170. C'est des tags "sans contact" 125khz, aussi appelé "megamos crypto", "Magic 1" (pour le EM4070) et "Magic 2" (pour le EM4170), ou encore ID48, et qui utilise un protocole cryptographique propriétaire (non public) à challenge, avec une clef de 96 bits. Il a été partiellement cassé [Dismantling Megamos Crypto: Wirelessly Lockpicking a Vehicle Immobilizer, Roel Verdult, D. Garcia, Barış Ege], mais le papier a été interdit par une décision de justice. On peut néanmoins trouver le papier, mais il ne donne pas tout l'algorithme cryptographique (une fonction est "délibérément omise"). C'est un tag utilisé sans beaucoup de système d'immobilisation de véhicules (dont le mien, d'où le jouage).

On peut trouver des lecteur de tags 125khz pas chers sur internet, qui semblent souvent basés sur [https://www.serasidis.gr/circuits/RFID_reader/125kHz_RFID_reader.htm]. Sous la main, j'ai un RDM6300, qui coûte 2 euros environ, et je veux communiquer avec le EM4170 de ma clef de voiture. le RDM6300 lit les puces EM4100 (du même constructeur que les EM4x70). L'EM4100 a un fonctionnement beaucoup plus simple: ils est en "lecture seule", et ne fait que de balancer son ID dès qu'il y a une porteuse de 125khz. Il ne faut donc pas s'occuper d'une communication traceiver->EM4100.

Pour jouer avec le EM4070 (ou le EM4170 qui est une version améliorée), il faut aussi pouvoir lui parler. Mais on peut "facilement" le modifier le lecteur "RDM6300" (ou similaire) pour pouvoir faire cela.

Déjà, j'ai changé la partie "MCU" de mon RDM6300 (les MCU des lecteurs super-cheap semblent de toutes façons pas assez puissant pour cela). J'utilise comme à mon habitude pour les petits projets un ESP8266. Sur le RDM6300, il faut couper et re-router vers l'ESP le signal pour la porteuse, et le signal lu. (Attention, le signal lu sort en 5V, donc faut le passer en 3.3v, chez moi avec des résistances). Il est bien sûr possible de faire un truc plus propre, mais faire des trucs propres ne m'amuse pas.

rdm_mod"rdm_esp"

Le principe, pour "envoyer" à l'EM4x70, est de couper la porteuse pendant quelques périodes pendant les 32 périodes de la porteuse qui correspondent à un bit. Pour envoyer une commande, il faut le faire au bon moment, au moment d'une "fenêtre" (LIW) de 160 périodes. Mais pas n'importe quand, et c'est pas très clair à quel moment il faut le faire en lisant le datasheet. Mais on peut essayer les 160 périodes et voir à peu près quand il faut envoyer les choses.

Voici un code qui demande l'id, et les données utilisateurs:

Bon, il y a quand même quelques "ratés" mais pas beaucoup comparé au niveau de bricolage du binz.

Pour essayer le challenge (authentification), le programme serait facilement modifiable, mais il faudrait connaître l'algorithme non public.

// put ESP8266 in 160mhz !

#include <ESP8266WiFi.h>
#include <Ticker.h>

#define CR D1 //125khz carrier
#define IN D2 //input

int tab[512];

int c=0;
int a,aa=0;
int oo=0;
int st=0;
int t=0;
int ot=0;


#define P0 0xffff000f
#define P1 0xffffffff

#define ID_MODE "0011" 
#define UM_MODE_1 "0101" 
#define UM_MODE_2 "1111" 
#define AUTH_MODE "0110" 
#define WRITE_MODE "1010" 
#define PIN_MODE "1001" 

char send[100]={0,0,0,0,1,1};
int possend=6;
int tosend=6;

void makesend(const char *x)
{
  send[0]=send[1]=0;
  for(int i=0;i<4;i++)
    send[i+2]=x[i]-'0';
  tosend=6;
}

unsigned int pat=P1; //pattern to send
int nb=0; //next bit

#define ST_NO 0 //nothing
#define ST_LIW 1 //wait LIW
#define ST_WAIT 2 //wait between LIW and send command
#define ST_CMD 3 //send cmd
#define ST_READ 4 //read

int st_wait=0;
int wait=128;

void affhex1(const char *s)
{
  int x=0;
  for(int u=0;u<4;u++) {
    x=2*x+s[u]-'0';
  }
  char c=x+'0';
  if(x>=10) c='A'+x-10;
  Serial.printf("%c",c);
}

void affhex(const char *s,int sz)
{
  if(sz<4) return;
  affhex1(s);
  affhex(s+4,sz-4);
}

char str1[512];
char str2[256];

int decode2(int sz)
{
  unsigned int y=1;
  while(y<sz) {
    str2[y/2]=str1[y];
    y+=2;
  }
  str2[y/2]=0;
  return y/2;
}


int decode(int *tab,int size)
{
  int z=0;
  for(int i=0;i<size;i++) {
    int b=tab[i];
    int a=i%2;
    if(b>48) break;
    int c=1;
    if(b>24) c=2;
    for(int u=0;u<c;u++)
      str1[z++]='0'+a;
    if(z>500) break;
  }    
  str1[z]=0;
  return z;
}

void ICACHE_RAM_ATTR onTimerISR(){
  digitalWrite(CR,nb);
  
  if(oo==0) {
    oo=1;
    nb=1;
    if(st==ST_WAIT) { //wait between the long modulation and starting to send the command
      if(st_wait<=0) {
	st=ST_CMD;
      }
      else 
	st_wait--;
    }

    if(st==ST_CMD) {
      if(pat==0) {
	pat=P1;
	if(possend<tosend) {
	  if(send[possend]==0)
	    pat=P0;
	  possend++;
	} else {
	  st=ST_READ;
	  t=0;
	  aa=0;
	}
      }
    }
    if(st==ST_CMD) {
      nb=pat&1;
      pat=(pat>>1);
    }
    return;
  }

  oo=nb=0;

  if(st!=ST_READ && st!=ST_LIW) return;
  
  a=digitalRead(IN);
  if(a==aa) {
    t++;
    return;
  }
  aa=a;

  
  if(st==ST_LIW) { //wait LIW
    if(t>50)  { // wait for the "long" modulation (64 perdiods) in the LIW
      st=ST_WAIT;
      st_wait=wait;
    }
    ot=t;
    t=0;
    return;
  }

  //assert(st==ST_READ)
  if(c<512) {
    tab[c]=t;
    t=0;
    c++;
  }  else
    st=ST_NO;
}

void setup()
{
    Serial.begin(115200);
    Serial.println("START");
    WiFi.mode(WIFI_OFF);
 
    pinMode(CR,OUTPUT);
    pinMode(IN,INPUT);

    timer1_attachInterrupt(onTimerISR);
    timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP);
    timer1_write(5*4); //250khz
}

void loop()
{
  delay(500);
  Serial.printf("wait=%d command=%d%d%d%d",wait,int(send[2]),int(send[3]),int(send[4]),int(send[5]));
  Serial.println();
  if(c==512) {
    int sd=decode(tab,512);
    int sd2=decode2(sd);
    affhex(str2+16,sd2-16);
    Serial.println();
  }
  Serial.flush();
  
  aa=0;
  t=0;
  c=0;

  static int p=0;
  p++;
  makesend(ID_MODE);
  if(p%3==1)
    makesend(UM_MODE_1);
  if(p%3==2)
    makesend(UM_MODE_2);

  possend=0;
  st=ST_LIW;
  //wait=(wait+1)%(32*5); //for "scan" of the best "wait"
}