Random IRC quote :      <ergot> tu revolucionas menos que un lavarropas roto

Análisis de la estructura interna del DNI-E

Hace unos días fuí a renovar mi DNI. Me dieron uno electrónico y me ofrecieron un lector para operar en internet a cambio de recibir una charla. No dejó de sorprenderme lo mucho que insistía la chica de la charla en que el DNI, internamente, solamente contenía los mismos datos que se pueden leer en el soporte físico, más dos claves públicas, la de autenticación y la de firmado. Insistió tanto que hasta resultaba sospechoso. Su insistencia apoyada por mi paranoia me impulsó a ponerme a enredar un poco con el DNI y el lector, a ver se podía sacar, y fruto de ello este artículo en el que comparto con vosotros lo que he podido ver y en el que resumo lo que he aprendido leyendo sobre este tema.

El lector es de la marca GEMALTO, modelo PC TWIN.

Junto con el lector se distribuye un cd con los drivers, documentación, certificados, software, apis para desarrollo e incluso código fuente.

El modelo del chip de la smartcard puede ser st19wl34 y ICC ST19wl34, y ejecuta un sistema operativo propio: DNIe v1.1.

 Para las pruebas vamos a usar el sniffer de usb, usbsnoob: http://sourceforge.net/projects/usbsnoop/

 En el cd de instalación se distribuye una libreria CTAPI para manejar el lector y poder comunicarse con la tarjeta, y algo de código de ejemplo que usaremos como base para nuestras pruebas.

Sobre Smartcards:

Para enredar con el DNIE recomiendo leer lo que se pueda del standard ISO 7816 (hay partes que hay que soltar pasta para conseguirlas) y el standard CWA-14890 (sobre seguridad).

El protocolo de comunicación con las tarjetas no es muy complicado, funciona mediante el intercambio de tramas (APDUs) comando – respuesta. Las APDUs comando tienen el formato CLA | INS | P1 | P2 | Lc | Data | Le (ya depende del comando que sea que haya datos, el formato de estos, etc..). Las APDUs respuesta tienen el formato SW1 | SW2 | Data (según la respuesta habrá datos o no).

La tarjeta implementa un sistema de archivos en el que hay DFs (dedicated files, equivalente a directorios) y EFs (elementary files, equivalente a ficheros). El directorio raiz es el MF (master file). Los archivos (tanto DFs como EFs) pueden ser identificados por nombre o por id (también por un path, que sólo es la concatenación de los ids de los DFs padre de la ruta hasta el DF o EF en cuestión). Puede ser que un archivo no tenga nombre, pero siempre tiene un id que lo identifica. Los ids van de 0 a 65535. El acceso a los ficheros puede ser restringido en función de varias políticas, entre ellas que el usuario esté o no identificado mediante su pin.

Las zonas del DNIE:

Según la web http://www.dnielectronico.es/ la información en el dni electrónico está distribuida en tres zonas:

 “ZONA PÚBLICA: Accesible en lectura sin restricciones, contenido:

 Certificado CA intermedia emisora.

Claves Diffie-Hellman.

Certificado x509 de componente.

 ZONA PRIVADA: Accesible en lectura por el ciudadano, mediante la utilización de la Clave Personal de Acceso o PIN, conteniendo:

Certificado de Firma (No Repudio).

Certificado de Autenticación (Digital Signature).

 ZONA DE SEGURIDAD: Accesible en lectura por el ciudadano, en los Puntos de Actualización del DNIe.

 Datos de filiación del ciudadano (los mismos que están en el soporte físico).

Imagen de la fotografía.

Imagen de la firma manuscrita.”

La zona pública:

Partiendo del software de ejemplo distribuido en el cd del lector vamos a implementar una función que recorra todos los ids de 0 a 65535 sin habernos identificado mediante el pin, guardándonos los ids que han sido seleccionables (es decir, no nos ha devuelto la respuesta “no encontrado”), los datos leídos en caso de haber podido leerlos y el FCI (File control information. Cuando seleccionamos un fichero podemos enviar a la tarjeta un comando GET_RESPONSE y ésta nos devolverá alguna información adicional sobre el fichero).

Para seleccionar un fichero por id usamos la instrucción A4. La respuesta 6A 82 significa que no se ha encontrado el fichero con ese id.

Los ids encontrados son:

815, 1280, 1536, 4415, 4608, 5439, 5456, 8032, 8288, 8448, 12640, 24928, 33120, 41312, 43104

Sabiendo que ids son seleccionables vamos a pedir el GET_RESPONSE de cada uno y a intentar hacer un READ_BINARY de los que sean EFs y se puedan acceder.

Cuando se selecciona un fichero la respuesta es del tipo: 61 XX

XX es el número de bytes que nos devolverá GET_RESPONSE.

El comando GET_RESPONSE es 00 C0 00 00 XX.

El comando READ_BINARY es 00 B0 SS SS NN:

NN número de bytes a leer.

SS SS offset del fichero en el que leer.

De todos los ids los siguientes son EFs que han sido encontrados pero que no hemos podido leer el contenido por política de seguridad:

File id = 1280 GET_RESPONSE: 0x6f 0x0c 0x85 0x0a 0x01 0x00 0x05 0x00 0x32 0x80 0xff 0xff 0xff 0xff 0x90 0x00 ?o???????2??????

File id = 4608 GET_RESPONSE: 0x6f 0x0d 0x85 0x0b 0x15 0x00 0x12 0x00 0x21 0xff 0xff 0xff 0xff 0xff 0x02 0x90 ?o???????!??????

File id = 8448  GET_RESPONSE: 0x6f 0x0c 0x85 0x0a 0x25 0x00 0x21 0x02 0x58 0xb1 0xff 0x80 0xd1 0xff 0x90 0x00 ?o???%?!?X??????

File id = 41312 GET_RESPONSE: 0x6f 0x0c 0x85 0x0a 0x01 0x60 0xa1 0x00 0x20 0x80 0x80 0xff 0xff 0xff 0x90 0x00 ?o????`?? ??????

Los siguientes son DFs encontrados:

File id = 4415 GET_RESPONSE: 0x6f 0x18 0x84 0x0a 0x49 0x43 0x43 0x2e 0x43 0x72 0x79 0x70 0x74 0x6f 0x85 0x0a ?o???ICC.Crypto? 0x38 0x3f 0x11 0x00 0x0a 0xff 0xff 0xff 0xff 0xff 0x90 0x00 to??8???????

File id = 5439 GET_RESPONSE: 0x6f 0x14 0x84 0x06 0x49 0x43 0x43 0x2e 0x49 0x44 0x85 0x0a 0x38 0x3f 0x15 0x00 ?o???ICC.ID??8??0x06 0xff 0xff 0xff 0xff 0xff 0x90 0x00 ID??8???

File id = 5456 GET_RESPONSE: 0x6f 0x1a 0x84 0x0c 0xa0 0x00 0x00 0x00 0x63 0x50 0x4b 0x43 0x53 0x2d 0x31 0x35 ?o???????cPKCS-1 0x85 0x0a 0x38 0x50 0x15 0x00 0x0c 0xff 0xff 0xff 0xff 0xff 0x90 0x00 15??8P????????

File id = 12640 GET_RESPONSE: 0x6f 0x18 0x84 0x0a 0x44 0x4e 0x49 0x65 0x2e 0x41 0x64 0x6d 0x69 0x6e 0x85 0x0a ?o???DNIe.Admin? 0x38 0x60 0x31 0x00 0x0a 0xff 0xff 0xff 0xff 0xff 0x90 0x00 in??8`1?????

 File id = 24928 GET_RESPONSE: 0x6f 0x16 0x84 0x08 0x44 0x4e 0x49 0x65 0x2e 0x50 0x75 0x62 0x85 0x0a 0x38 0x60 ?o???DNIe.Pub??8 0x61 0x00 0x08 0xe0 0xff 0xff 0xff 0xff 0x90 0x00 ub??8`a???

File id = 33120 GET_RESPONSE: 0x6f 0x17 0x84 0x09 0x44 0x4e 0x49 0x65 0x2e 0x50 0x72 0x69 0x76 0x85 0x0a 0x38 ?o???DNIe.Priv?? 0x60 0x81 0x00 0x09 0xe0 0xff 0xff 0xff 0xff 0x90 0x00 iv??8`?????

Todos estos DFs tienen estos nombres:

4415, ICC.Crypto

5439, ICC.ID

5456, cPKCS-115

12640, DNIe.Admin

24928, DNIe.Pub

33120, DNIe.Priv

Todos cuelgan del Master.File, que siempre tiene id 0x3f:

File name = Master.File  GET_RESPONSE:  0x6f 0x19 0x84 0x0b 0x4d 0x61 0x73 0x74 0x65 0x72 0x2e 0x46 0x69 0x6c 0x65 0x85 ?o???Master.File 0x0a 0x38 0x3f 0x00 0x00 0x0b 0xff 0xff 0xff 0xff 0xff 0x90 0x00 le??8????????  

A continuación están los EFs que sí hemos podido leer (se muestran con la respuesta al comando GET_RESPONSE y READ_BINARY):

File id = 815 GET_RESPONSE: 0x6f 0x0c 0x85 0x0a 0x01 0x2f 0x03 0x00 0x28 0x00 0x80 0xff 0xff 0xff 0x90 0x00 ?o????/??(?????? READ_BINARY: 0x44 0x4e 0x49 0x65 0x20 0x30 0x31 0x2e 0x31 0x33 0x20 0x41 0x31 0x31 0x20 0x48 ?DNIe 01.13 A11 0x20 0x34 0x43 0x33 0x34 0x20 0x45 0x58 0x50 0x20 0x31 0x2d 0x28 0x28 0x34 0x2e H 4C34 EXP 1-((40×32 0x2d 0x35 0x29 0x29 0x00 0x00 0x00 0x90 0x00 1-((4.2-5)

File id = 1536 GET_RESPONSE: 0x6f 0x0c 0x85 0x0a 0x01 0x00 0x06 0x00 0x19 0x00 0xff 0xff 0xff 0xff 0x90 0x00 ?o??????????????READ_BINARY: El IDESP. Varía entre cada DNI.

File id = 8032 GET_RESPONSE: 0x6f 0x0c 0x85 0x0a 0x01 0x60 0x1f 0x03 0x25 0x00 0xff 0xff 0xff 0xff 0x90 0x00 ?o????`??%??????READ_BINARY: Certificado. Varía entre cada DNI.

File id = 8288 GET_RESPONSE: 0x6f 0x0c 0x85 0x0a 0x01 0x60 0x20 0x04 0x2c 0x00 0xff 0xff 0xff 0xff 0x90 0x00 ?o????` ?,??????READ_BINARY: 

(Convertido con openssl) 

Certificate:Data:

Version: 3 (0x2)

Serial Number:

1a:ed:35:7a:7b:a8:c8:7b:43:fa:df:3f:a9:77:56:80

Signature Algorithm: sha1WithRSAEncryption

Issuer: C=ES, O=DIRECCION GENERAL DE LA POLICIA, OU=DNIE, OU=AC RAIZ COMPONENTES, CN=000000006573524449600006

Validity

Not Before: Feb 21 09:37:03 2006 GMT

Not After : Feb 20 17:13:15 2031 GMT

Subject: C=ES, O=DIRECCION GENERAL DE LA POLICIA, OU=DNIE, OU=FNMT, CN=C COMPONENTES 001

Subject Public Key Info:

Public Key Algorithm: rsaEncryption

RSA Public Key: (1024 bit)

 Exponent: 65537 (0x10001)

X509v3 extensions:

X509v3 Basic Constraints: critical

CA:TRUE, pathlen:0

X509v3 Subject Key Identifier:

3F:32:0E:97:94:B8:F9:56:6E:A3:D7:21:A6:17:54:FA:92:3A:12:B1

X509v3 Authority Key Identifier:

keyid:45:D7:64:65:F2:25:04:D8:04:5E:66:27:41:9D:76:50:05:A2:D1:8

 X509v3 Key Usage: critical

Certificate Sign, CRL Sign

X509v3 Certificate Policies:

Policy: X509v3 Any Policy

CPS: http://www.dnie.es/dpc

 X509v3 CRL Distribution Points:

URI:http://crls.dnie.es/crls/ARLCOM.crl

URI:ldap://ldap.dnie.es/CN=CRL,CN=000000006573524449600006,OU=A%20RAIZ%20COMPONENTES,OU=DNIE,O=DIRECCION%20GENERAL%20DE%20LA%20POLICIA,C=ES?auhorityRevocationList?baseobjectclass=cRLDistributionPoint

File id = 43104 GET_RESPONSE: 0x6f 0x0c 0x85 0x0a 0x01 0x60 0xa8 0x00 0xb5 0x00 0xff 0xff 0xff 0xff 0x90 0x00 ?o????`????????? READ_BINARY: Otros datos (sin interpretar). Varían entre cada DNI.

El fichero con ID 815 contiene la versión del DNIE. Estos datos varían con cada DNIE si cambia la versión de éste (En la lectura de arriba: DNIe 01.13 A11H 4C34 EXP 1-((4.2-5). En otro DNIE: DNIe 01.13 B11H 4C34 EXP 1-(3.5-3)).

 El fichero con ID 1536 contiene el IDESP del DNIE. Este dato se puede ver en la carátula del DNIE también.

El fichero con ID 8032 contiene un certificado (certificado de componente) que es leído y usado por la interfaz cuando se va a establecer el canal seguro con la tarjeta. Este certificado varía entre cada DNIE.

El fichero con ID 8288 también contiene un certificado (certificado raíz CA de componente), que no varía entre DNIE.

 El fichero con ID 43104 contiene 0xB0 bytes de datos (cuyo significado desconozco) que también varían entre cada DNIE.

Datos públicos accesibles:

La zona privada:

Para acceder a la zona privada es necesario introducir el pin de usuario a la tarjeta con el comando VERIFY. El comando VERIFY tiene un formato sencillo: 00 20 00 00 Lc Data. En data iría el PIN. Sin embargo el DNIE restringe el uso de algunos comandos si estos no se envían sobre un canal seguro (usando Secure Messaging). Para quien tenga curiosidad en el artículo original se explica todo el procedimiento para establecer en canal seguro.

Implementar el establecimiento del canal seguro y cifrado para Secure Messaging es bastante lio. Así que finalmente me decanto por empezar a desensamblar la dll que se carga con internet explorer (c:\windows\system32\UsrDNIeCertStore.dll) y que se encarga de logearse en la tarjeta y leer los datos privados cuando hace falta identificarse en alguna web que soporte identificación por DNIe.

Encontramos en el binario varias cadenas llamativas:

La dll está compilada con la librería cryptopp (http://www.cryptopp.com).

Vamos al fuente des.cpp de cryptopp (http://www.trolocsis.com/crypto++/des_8cpp-source.html). En seguida encontramos la parte en ensamblador equivalente al fuente:

Localizamos el resto de las funciones. La que más nos interesa es la que cifra y descifra los bloques:

También vamos a localizar la parte donde envia y recibe Comandos-Respuestas a la tarjeta para poder seguir el log que vamos a hacer después:

Ya tenemos localizadas todas las partes que nos interesan.

Con el script para ida en python que viene a continuación pondremos breakpoints en cada parte interesante (en la entrada de la función de cifrado / descifrado para capturar los datos antes de pasar por el algoritmo; en la salida de la misma función para capturar los datos trás ser aplicado el algoritmo; y antes y despúes de llamar a SCardTransmit para capturar los datos enviados a la tarjeta y la respuesta recibida), y en el hook de breakpoints leeremos la zona de memoria donde están los datos enviados a / recibidos desde la tarjeta, y los datos cifrados y su correspondiente descifrado. El script también se guardará una lista de pares de datos cifrados y su correspondiente descifrado para luego aplicarlo a los datos capturados en SCardTransmit y poder ver bien lo que la dll está leyendo de la tarjeta.

El script para hookear en las funciones de cifrado: 

from idaapi import *
import ctypes
import struct
def writelogitem(f,e):
  f.write("\n————————————————————————\n")
  f.write(e[0]+"\n")
  e=e[1]
  i=0
  for ee in e:
    f.write(("%02X " % ee))
    i+=1
    if i%16==0:
      f.write("\n")
  f.write("\n")
  i=0
  for ee in e:
    try:
      if ee>=0x20 and ee<0x7e:
        f.write(chr(ee))
      else:
        f.write(".")
    except:
      pass
    i+=1
    if i%32==0:
      f.write("\n")
def readmem(ea,sz):
    global processHandle
    #buffer = ctypes.c_char_p("_"*sz)
    buffer = ctypes.create_string_buffer(sz)
    bytesRead = ctypes.c_ulong(0)
    bufferSize =  sz
    ctypes.windll.kernel32.ReadProcessMemory(processHandle, ea, buffer, bufferSize, ctypes.byref(bytesRead))
    arr=[]   
    for i in range(0,bytesRead.value):
      arr.append(ord(buffer[i]))
    return arr
def readdword(ea):
    global processHandle
    #buffer = ctypes.c_char_p("_"*4)
    buffer = ctypes.create_string_buffer(4)
    bytesRead = ctypes.c_ulong(0)
    bufferSize =  4
    ctypes.windll.kernel32.ReadProcessMemory(processHandle, ea, buffer, bufferSize, ctypes.byref(bytesRead))
    return struct.unpack("L",buffer.raw)[0]  
class MyDbgHook(DBG_Hooks):
    def dbg_process_start(self, pid, tid, ea, name, base, size):
        global processHandle     
        processHandle = ctypes.windll.kernel32.OpenProcess(0x1F0FFF, 0, pid)   
        print "process handle!"
        print processHandle   
        return 0      
    def dbg_process_attach(self, pid, tid, ea, name, base, size):
        print "process attach"
        return self.dbg_process_start(pid, tid, ea, name, base, size)
    def dbg_process_exit(self, pid, tid, ea, code)
        global logs
        global logstypes
        global ProcessAndXorBlockInputOutput
        duplogs=[]
        for e in logs:
          dupe=[]
          for ee in e[1]:
            dupe.append(ee)
          tmparr=[]
          tmparr.append(e[0])
          tmparr.append(dupe)
          duplogs.append(tmparr)
        for e in ProcessAndXorBlockInputOutput:
          tempe=""
          for ee in e[0]:
            if ee>=0x20 and ee<0x7e:
              tempe+=chr(ee)                
          if "Mast" not in tempe and\
             "File" not in tempe and\
             "cPKC" not in tempe and\
             "Cert" not in tempe and\
             "Autentic" not in tempe and\
             "acion" not in tempe and\
             "37C11A78" not in tempe and\
             "0B312010" not in tempe and\
             "01181105" not in tempe and\
             "ES1" not in tempe:
            nl=0            
            for l in logs:
              if l[0]=="———<<<RESPONSE<<<———-":
                i = 0
                while i + 8 <= len(l[1]):
                  if l[1][i:i+8]==e[0]:
                    l[1][i]=e[1][0]
                    l[1][i+1]=e[1][1]
                    l[1][i+2]=e[1][2]
                    l[1][i+3]=e[1][3]
                    l[1][i+4]=e[1][4]
                    l[1][i+5]=e[1][5]
                    l[1][i+6]=e[1][6]
                    l[1][i+7]=e[1][7]
                    logs[nl]=l
                    break
                  else:
                    i+=1
              nl+=1
        f=open("c:\\logs.txt","w")   
        nl=0    
        for l in logs:
          writelogitem(f,l)
          if l[0]=="———<<<RESPONSE<<<———-":
            duplogs[nl][0]="———<<<CRYPTED RESPONSE<<<———-"
            writelogitem(f,duplogs[nl])
          nl+=1     
        return 0
    def dbg_process_detach(self, pid, tid, ea):
      return self.dbg_process_exit(pid, tid, ea, 0)
    def dbg_library_load(self, pid, tid, ea, name, base, size):
        print "loaded:"+name
        return 0
    def dbg_bpt(self, tid, ea):      
        global logs
        global logstypes
        global LastProcessAndXorBlockInput
        global ProcessAndXorBlockInputOutput
        global LastRecvLenPtr
        global LastRecvBuffer     
        arr=[]
        if ea==0x1A7F4BD:
          LastRecvLenPtr = GetRegValue("ECX")   
        if ea==0x1A7F4C9:
          LastRecvBuffer = GetRegValue("EDX")     
        if ea==0x1A5DF95: #ea==0x1A5E285 or
          arr.append("<a>\\\\\\\\\\\\ProcessAndXorBlock\\\\\\\\\\\\</a>")
          r = GetRegValue("EAX")
          LastProcessAndXorBlockInput=readmem(r,8)
          arr.append(LastProcessAndXorBlockInput)
          logs.append(arr)    
        if ea==0x1A5E02E or ea==0x1A5E03E: #ea==0x1A5E306 or ea==0x1A5E2F6 or
          arr.append("//////ProcessAndXorBlock//////")
          r = GetRegValue("ECX")
          temp=[]
          temp.append(LastProcessAndXorBlockInput)
          temp.append(readmem(r,8))
          ProcessAndXorBlockInputOutput.append(temp)
          arr.append(temp[1])
          logs.append(arr)   
        if ea==0x1A7F4CF:
          arr.append("———>>>COMMAND>>>———-")
          r1 = GetRegValue("EDI")
          r2 = GetRegValue("EBP")
          arr.append(readmem(r1,r2))
          logs.append(arr)    
        if ea==0x1A7F4D7:
          arr.append("———<<<RESPONSE<<<———-")
          len=readdword(LastRecvLenPtr)
          arr.append(readmem(LastRecvBuffer,len))
          logs.append(arr)      
        continue_process()
        return 0
    def dbg_trace(self, tid, ea):
        return 0     
    def dbg_step_into(self):
        return 0  
    def dbg_step_over(self):
        return 0
try:
    if debughook:
        print "Removing previous hook …"
        debughook.unhook()
except:
    pass
# Install the debug hook
debughook = MyDbgHook()
debughook.hook()
debughook.steps = 0
logstypes=[]
logs=[]
LastRecvLenPtr=None
LastRecvBuffer=None
LastProcessAndXorBlockInput=None
ProcessAndXorBlockInputOutput=[]
processHandle=None
#SCardTransmit
add_bpt(0x1A7F4CF,0,BPT_SOFT)
enable_bpt(0x1A7F4CF,True)
»
#CryptoPP_DES_Base_ProcessAndXorBlock (entrando)
add_bpt(0x1A5E285,0,BPT_SOFT)
enable_bpt(0x1A5E285,True)
#CryptoPP_DES_Base_ProcessAndXorBlock (saliendo)
add_bpt(0x1A5E306,0,BPT_SOFT)
enable_bpt(0x1A5E306,True)
#CryptoPP_DES_Base_ProcessAndXorBlock (saliendo)
add_bpt(0x1A5E2F6,0,BPT_SOFT)
enable_bpt(0x1A5E2F6,True)
»
#CryptoPP_DES_EDE2_Base_ProcessAndXorBlock (entrando)
add_bpt(0x1A5DF95,0,BPT_SOFT)
enable_bpt(0x1A5DF95,True)
#CryptoPP_DES_EDE2_Base_ProcessAndXorBlock (saliendo)
add_bpt(0x1A5E02E,0,BPT_SOFT)
enable_bpt(0x1A5E02E,True)
#CryptoPP_DES_EDE2_Base_ProcessAndXorBlock (saliendo)
add_bpt(0x1A5E03E,0,BPT_SOFT)
enable_bpt(0x1A5E03E,True)
#Keep last recv len ptr
add_bpt(0x1A7F4BD,0,BPT_SOFT)
enable_bpt(0x1A7F4BD,True)
#Keep last recv buffer
add_bpt(0x1A7F4C9,0,BPT_SOFT)
enable_bpt(0x1A7F4C9,True)
#Keep response
add_bpt(0x1A7F4D7,0,BPT_SOFT)
enable_bpt(0x1A7F4D7,True)
# Start debugging
run_requests()

Análisis de la captura:

Con todo listo nos ponemos a depurar la dll (con internet explorer como host) conectando a la página de la dgt para consultar los puntos usando el DNIe (https://aplcr.dgt.es/WEB_COPACI/certificado/verSaldoPuntosCert.faces ):

Aquí están los EFs y DFs accedidos en la tarjeta en esta prueba:

Fichero Master.File -> cPKCS-15 -> 0x160:  

.i.0…KprivAutenticacion….0…123456789A123456789A123456789.. -> ID clave autenticación

.0………….0.0…?………………………..2C

…i.0…KprivFirmaDigital….0…123456789A123456789A12345678A… -> ID clave firmado

0@…………0.0…?……………………….<0….

 Fichero Master.File -> cPKCS-15 -> 0x460

Un trozo:

…..0…CertAutenticacion….0. -> Trozo certificado X.509 autenticación

..123456789A123456789A123456789…0..0…`.p……..0w1.0…U….ES1.0…U….11111111K1.0…U….APELLID1.0…U.*..PERICO110/..U…(APELLID APELLID, PERICO (AUTENTICACI..N).^0\1.0…U….ES1(0&..U….DIRECCION GENERAL DE…LA POLICIA1.0…U….DNIE1.0…U….AC DNIE 003..D………./…..

Otro trozo:

…..0…CertFirmaDigital….0.. -> Trozo certificado X.509 firmado

.123456789A123456789A12345678A…0..0…`.p……..0n1.0…U….ES1.0…U….11111111K1.0…U….APELLID1.0…U.*..PERICO1(0&..U….APELLID APELLID, PERICO(FIRMA).^0\1.0…U….ES1(0&..U….DIRECCION GENERAL DE LA POLICI.u.a.A1.0…..).U….DNIE1.0…U….AC DNIE 003..D…………U<.b..

Otro trozo:

…..0…CertCAIntermediaDGP…@ -> Trozo certificado X.509 CA Intermedia0

S0637C11A780B3120100118110507…0..0…`ap……..0\1.0…U….ES1(0&..U….DIRECCION GENERAL DE LA POLICIA1.0…U….DNIE1.0…U….AC DNIE 003._0]1.0…U….ES1(0&..U….DIRECCION GENERAL DE LA POLICIA1.0…U….DNIY..a..).U….AC RAIZ DNIE..|I..p…D..n..H…………+l.t..

Fichero Master.File -> DNIe.Priv -> 0x470:

<<Bloque de datos sin interpretar>>

Fichero Master.File -> DNIe.Priv -> 0x570:

<<Bloque de datos sin interpretar>>

Fichero Master.File -> DNIe.Pub -> 0x670:

<<Bloque de datos sin interpretar>>

Los ficheros 0x470, 0x570 que cuelgan de DNIe.Priv, y el fichero 0x670, que cuelga de DNIe.Pub, creo que contienen el resto de los datos para los certificados de autenticación, de firmado y el de la CA intermedia, respectivamente.

Resultados:

Aquí están todas las partes del DNIE que hemos podido ver el contenido:

A parte de lo anterior cuando hicimos el recorrido de ficheros sin habernos validado, encontramos cuatro ids que la tarjeta nos respondió que existían 1280, 4608, 8448 y 41312, pero que no pudimos leer el contenido.

En las pruebas que hemos hecho hemos visto que no es muy complicado hookear en las funciones principales de cifrado y descifrado DES en la dll que se carga con los navegadores. Cualquier troyano podría facilmente inyectarse en Internet Explorer, buscar las funciones de cifrado DES y hookear en ellas para pillar todos los datos intercambiados descifrados (entre ellos, datos que podrían ser comprometidos como claves públicas de autenticación y firmado, nombre y número de dni, idesp, etc…) sin necesidad de conocer el pin ni realizar ninguna implementación de código complicada. El pin también sería sencillo de capturar en texto plano para un troyano con estos hooks, esperando el comando VERIFY (0C 20 …) y mirando los últimos cifrados:

Lo que se queda en el tintero:

Hemos podido explorar una parte de la estructura interna del DNI electrónico, aunque todavía se nos han quedado zonas pendientes para las cuales haría falta implementar el recorrido post-validación del pin de todos los ficheros por id (0-65535).

 Según la web del DNIE existe otra zona, llamada zona de seguridad:

“ZONA DE SEGURIDAD: Accesible en lectura por el ciudadano, en los Puntos de Actualización del DNIe.

  • Datos de filiación del ciudadano (los mismos que están en el soporte físico).
  • Imagen de la fotografía.
  • Imagen de la firma manuscrita.”

Estaría bien comprobar si realmente los ids donde se almacenan estos datos son solamente accesibles desde los Puntos de Actualización en las comisarias, o simplemente es que el software que distribuyen no permite recuperar esta información (pero sí que está accesible, una vez validados). Como hemos visto en los logs, alguna de la información que aparece en el soporte físico (nombre y apellidos, número de DNI, IDESP) puede deducirse de los datos accesibles.

Respecto a fuzzing contra la tarjeta creo que sería bastante complicado sacar fallos en el software que se ejecuta en la smartcard y más explotarlo, al menos con los medios y la información a la que tenemos acceso (al menos yo). Otra cosa sería buscar fallos en el software lector (por ejemplo la dll que decíamos antes, UsrDNIeCertStore.dll. Mientras depuraba he visto que hacía bastantes guarradas como usar el contenido de variables sin inicializar en la primera vuelta de un bucle y cosas parecidas, así que no me extrañaría que sonara la flauta). Hay tarjetas intermedias que aceptan una smartcard como entrada y a la vez guardan la interfaz de las smartcard por el otro, haciendo de puente, donde se podría implementar un fuzzer sencillo cambiando bytes aleatorios de las APDUs intercambiadas. O hookear en el driver o la dll y modificar las APDUs ahí.

Y hasta aquí el artículo. Sorry si es un poco ladrillo, espero que no haya sido mucho tostón 😉 Para quien todavía se haya quedado con ganas de mirar más, aquí he dejado una versión en pdf un poco más extendida en la que se explica todo más en detalle: http://blog.48bits.com/wp-content/uploads/2010/03/articulo.pdf. Si alguien tiene el lector y quiere el código de la aplicación que he usado para pruebas, que me lo pida. Y respecto a documentación adicional sobre smartcards y concretamente sobre el DNIE, os recomiendo estos links:

http://www.dnielectronico.es/seccion_integradores/index.html

http://www.dnielectronico.es/seccion_integradores/cwa14890-02-2004-May.pdf

http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4.aspx

Un saludo.

53 Comentarios para “Análisis de la estructura interna del DNI-E”

  1. Comment por auron | 01/11/11 at 5:27 pm

    Buenas tardes, me parece interesante tu articulo, me preguntaba si podrias enviarme por favor el codigo en C++ para probar estas cosillas 😀
    gracias

  2. Comment por antonio | 01/11/11 at 5:40 pm

    Buenas tardes. me parece un articulo muyn interesante, me preguntaba si podrias enviarme el codigo en C+ para testearlo con mi lector de DNIe. Un saludo y muchas gracias


Se han cerrado los comentarios