Random IRC quote :      <matalaz> total, es dilatar el culo y yast <infi> pues dilatalo tu xD

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 Karcrack | 03/16/10 at 10:03 am

    Muy buen trabajo =D
    El paper mejor explicado que he podido encontrar muy bueno 😉

  2. Comment por Mario Ballano | 03/16/10 at 10:40 am

    Muy currao si senyor!!!! :)))))

  3. Comment por Miguel | 03/16/10 at 11:13 am

    /*++
    Muy bueno
    plas plas plas
    –*/

  4. Comment por Luis Corrons | 03/16/10 at 11:14 am

    Muy bueno Javi, me ha gustado 🙂

  5. Comment por Shaddy | 03/16/10 at 11:16 am

    Muy bueno tío!!

  6. Comment por matalaz | 03/16/10 at 11:18 am

    Está de puta madre Javi, enhorabuena 🙂

  7. Comment por Alon | 03/16/10 at 11:18 am

    Muy bueno!!! buen trabajo

  8. Comment por K Budai | 03/16/10 at 11:40 am

    ¡Genial!
    ¿Para cuando un análisis de los RFID del pasaporte?
    Con ese me siento verdaderamente paranoico.

  9. Comment por Eloi Sanfèlix | 03/16/10 at 1:50 pm

    Buen artículo, enhorabuena!

    Yo también ando a mitad camino de mis investigaciones al respecto… aunque mi objetivo es conectar el DNIe al osciloscopio y ver qué puedo obtener con él 🙂

    Cuando tenga algo de tiempo quiero implementar un script o programa que establezca el canal seguro. Entiendo que todavía no lo has hecho no?

    Si lo tienes estaría bien que lo compartieras y me ahorro el trabajo 😉 Si no, a ver cuando puedo compartirlo yo. Supongo que has visto que la SK.IFD.AUT está a la vista de todos en los propios drivers junto con su certificado, al menos en los drivers para OpenSC.

    Respecto a fallos lógicos en la smart card en sí, yo personalmente tampoco espero que haya nada sencillo que rasgar… quizás combinando ataques lógicos e inyección de fallos mediante glitching, depende un poco del buen trabajo que hayan hecho durante la certificación.

    Ya veremos cuando tengo tiempo de jugar con él y si puedo aportar algo al respecto.

    @ K Budai, quizás estas slides sobre el ePassport te resulten interesantes: http://www.cs.ru.nl/~erikpoll/hw/slides/ePassport.pdf

    El resto de slides también explican muchas cosillas sobre smart cards, RFIDs y demás.

    Saludetess

  10. Comment por Seifreed | 03/16/10 at 3:54 pm

    Hola!

    Muy interesante el artículo, espero leer la siguiente parte

    Un saludo

  11. Comment por Dreg | 03/16/10 at 4:35 pm

    Que nivel! 🙂

  12. Comment por Ismael Briones | 03/17/10 at 4:33 am

    Muy bueno el artículo! Enhorabuena. A ver si me hago con un lector para mi dni 🙂

  13. Val
    Comment por Val | 03/17/10 at 6:12 am

    Efectivamente, no hay mucho más que rascar:

    De todas formas, no hace falta tanto lío, hay mucha más información pública a propósito:

    Es más sencillo leerse el «estándar» de facto denominado pkcs#15 de los laboratorios RSA, y acceder a ese directorio público, que es una especie de «tabla de localización de archivos» sobre el contenido de la tarjeta.

    En él se pueden leer muchos ficheros que también son públicos a propósito, y en los cuales está apuntado el contenido de la tarjeta, y que no es secreto, ni mucho menos.

    Hay que fijarse que he dicho «ficheros que apuntan el contenido de la tarjeta», pero no que se pueda leer el contenido de los fincheros aquí apuntados. Eso es lo que realmente se ha protegido. Repito. Eso es lo que realmente se ha protegido.

    Saludos,

  14. Comment por La Nuri | 03/17/10 at 7:00 am

    Si yo os contara por donde me paso el dni electronico… Quien quiere hacer pruebas con el mio? Primero tendra que quitarmelooo

  15. Comment por Boken | 03/17/10 at 11:23 am

    Muy bueno el post!! Muchas gracias por el estudio, ha sido muy bueno, ahora a fuzzear que toca ;D

    Saludos.

  16. Comment por pretos | 03/19/10 at 4:35 pm

    Enhorabuena, muy interesante el articulo!

    Como una ampliacion al gran articulo que has hecho, queria comentarte un par de cosas sobre el protocolo CWA, por si te interesan:
    – Las operaciones de firma y cifrado asimetricas, necesarias en el protocolo CWA, son simples operaciones exponenciales:
    Firma digital: (DATOS_a_firmar^private_exponent) mod modulus
    Cifrado asimetrico: (DATOS_a_cifrar^public_exponent) mod modulus
    para realizar esas operaciones yo use la funcion modPow de BigInteger en Java.

    – Hace unos meses implemente el protocolo CWA (escogi la otra version «Device authentication with privacy protection») para ver que cosas se podian hacer con la tarjeta, hasta ahora he podido: verificar el PIN, cambiar el PIN, leer los certificados de firma y autenticacion que hay en el DF DNIe_Priv, y hacer operaciones criptograficas: firma digital (ya no tengo que pasar por la «piedra» de las librerias cerradas de la DGP), cifrado simetrico, hash, wrap,.., aun asi esta autenticacion mutua con la tarjeta no me da privilegios suficientes para leer los ficheros del DF DNIe_Admin.

    – Efectivamente, como dice Eloi, la clave privada para la autenticacion interna (SK.IFD.AUT) se encuentra dentro de los drivers que hay en la pagina del DNIe, de hecho en las librerias se encuentra metida toda la informacion para implementar el protocolo: indentificadores de seleccion de claves, certificados, exponente privado, modulo…

    Teniendo esto a la vista (backtrack + IDA) no sé porqué guardan con tanto recelo esta informacion los de la DPG, el año pasado en las jornadas de presentacion de los perfiles de proteccion dijeron que iban a sacar una plataforma para la obtencion de estas claves y una guia de comandos del DNIe, ¡¡estaba prevista para Junio del año pasado!!.

    Gran trabajo, un saludo.

  17. Comment por Barracuda | 03/21/10 at 5:46 am

    ¿Quiere alguien explicarme para que sirve el DNI electrónico? ¿Puedo utilizarlo para es para lo que sirva sin disponer del tarjetero? ¿Que consigue la administración implantando esta nueva tarjetita/chip? ¿Va en detrimento del ciudadano o hay posibilidad de que en el futuro así sea?
    Gracias de antemano.

  18. Q
    Comment por Q | 03/21/10 at 6:52 am

    Magnifico trabajo. Muchas gracias por compartirlo.

    Y lo mismo para las no menos interesantes contribuciones que algunos habeis dejado en los comentarios.

    Gracias.

  19. Comment por pepe botella | 03/21/10 at 7:07 am

    En donde dices «Las APDUs respuesta tienen el formato SW1 | SW2 | Data (según la respuesta habrá datos o no).», date cuenta que has puesto el formato al reves. Los datos van primero y la palabra de estado siempre va al final: Data | SW1 | SW2

  20. Comment por juanpines | 03/21/10 at 6:59 pm

    Muy bueno el articulo.

  21. Comment por Arri | 03/21/10 at 7:10 pm

    Impresionante tío…
    Me q

  22. Comment por Arri | 03/21/10 at 7:10 pm

    (…decía que) me quito el sombrero!

  23. Comment por Ruben | 03/21/10 at 7:48 pm

    Caso elegante de aplicación de ingeniería inversa donde los haya. Genial javi!

  24. Comment por Pancho | 03/21/10 at 8:09 pm

    Muy bueno! Gracias por compartirlo!

  25. Comment por Cerdo Justiciero | 03/21/10 at 8:44 pm

    ¡Eres mi nuevo dios!

  26. Comment por djcarras | 03/21/10 at 8:59 pm

    excelente trabajo…

  27. Comment por chemalogo | 03/22/10 at 5:05 am

    Impresioante. Muy técnico para mi, pero impresionante.

    Una cosa que he echado en falta es indicar que dentro del DNIe también hay datos sobre huella dactilar, con dos propósitos:
    – el primero, que sirva de PUK (clave de desbloqueo) cuando introducimos el PIN (clave de acceso personal) erróneamente tres veces consecutivas.
    – el segundo, como en los antiguos DNIs, llevar esa información con uno mismo por si hace falta alguna comprobación de identidad adicional.

  28. Comment por Ivan Camara | 03/22/10 at 5:23 am

    Muy bueno, gracias por compartir.

  29. Comment por Juanmi | 03/22/10 at 5:26 am

    Bravo, un trabajo espectacular.

    Mil gracias por compartir 😉

  30. Comment por Igor | 03/22/10 at 7:43 am

    Se agradece la información. Mil gracias.

  31. Comment por Anónimo | 03/22/10 at 7:49 am

    Muy bueno, una pregunta indiscreta….¿ccuánto te dejaste en la lectora?

    Saludos!

  32. Comment por aceitunas | 03/22/10 at 7:49 am

    Muy bueno, una pregunta indiscreta….¿ccuánto te dejaste en la lectora?

    Saludos!

  33. Comment por Zacarito | 03/22/10 at 2:41 pm

    Muy bueno Random. Esto me recuerda aquella otra experiencia que muchos tuvimos cuando una plataforma de TV digital nos entregó una tarjetita con un chip al que resultaba «imposible» acceder sin pagar. Tras meses de educación muchos consiguieron abrir todos (digo bien to_dos) los canales codificados. El invento podía hacerse con una simple tarjeta llamada picard, un programador muy simple T-21 y el propio mando del decodificador. Osea, que con 300 de las antiguas pesetillas veías hasta lo que no querías ver. El hallazgo corrió como la pólvora por la red y funcionó durante varios años (a pesar de un sin fin de contramedidas y cambios de chips). Quien sabe siquizá algún día podremos pagar las multas de tráfico o los impuestos con una tarjeta de DIGITAL+, y poder exclamar: “¡Qué pague Polanco!”…

  34. Comment por Maktutote | 03/22/10 at 2:52 pm

    Impresionante!!!

    Da gusto leer artículos así 😀

  35. Comment por YoSerHumano | 03/23/10 at 4:13 am

    Artículo GENIAL.

    Yo tengo implementado desde hace un tiempo el leer el nº de DNI, nombre y apellidos a partir de la lectura del fichero 0x6004 (PKCS15 Certificate Directory File)…. no obstante estoy francamente interesado en terminar con el oscurantismo de la DGP y que sea pública de una vez el API de acceso al DNIe.

    Apunto idea para gente con más tiempo que yo… destripar la pkcs11.dll/so distribuida para el firefox.

    Saludos a todos.

  36. Comment por Alejandro Rivero | 03/24/10 at 4:16 pm

    Pues yo sigo sin poder ver mis certificados. Usease,
    pkcs15-tool -c
    me lista CertAutenticacion CertFirmaDigital y CertCAIntermediaDGP pero solo puedo sacar este ultimo, haciendo
    pkcs15-tool -r 5330363…
    En cambio, si intento lo mismo con los cualquiera de los otros dos, me da el error
    Certificate read failed: Invalid ASN.1 object

  37. Comment por Alejandro Rivero | 03/24/10 at 5:49 pm

    Y oye chico, no veo que el 0x6004 me permita sacar nombre y apellidos. Ese asn1, que puedes decodificar facilmente con opensc-explorer, tan solo contiene lo que dice ser, el CFD, usease los nombres de los tres certificados CertAutenticacion CertFirmaDigital CertCAIntermediaDGP, su ID y su path. Luego puedes ir a la zona publica 6061 y sacar el certificado CertCAIntermediaDGP, pero los otros dos estan en 6081 y parece que se niegan a salir, ni con esta ni con el pkcs15-tool -r

  38. Comment por Alejandro Rivero | 03/25/10 at 6:22 am

    Bueno, viendo de nuevo el articulo y comparando con mis pruebas, diria que lo que me pasa es que las herramientas de opensc, al menos en la version que estoy usando, no saben tampoco establecer un canal seguro.
    De todas formas me parece absurdo que los certificados -sin las claves privadas- exigan tal canal, y/o que exigan el mismo PIN que para una operacion de firma, no hace mas que generar confusion. (Segun algunos usuarios, en el caso practico -que no deja de ser confuso- lo que ocurre es que el PIN se pide una vez para acceder a todo y luego otra vez en cada operacion de firma con CertFirmaDigital, pero vete a saber si eso es cosa fija, de la tarjeta, del ejemplo, o del driver).
    Pues eso, si alguien tiene un ejemplo de codigo para acceder al contenido de los certificados (y no a 0x6004, que es simplemente el CFD) a ver su se anima a ponerlo por algun sitio

  39. Comment por Alejandro Rivero | 03/25/10 at 1:43 pm

    Bueno, ya casi lo he entendido todo. Parece que en algun lado hay un lio de endian. Lo que openSC describe como OpenSC [3F00/5015]> cat 6004 tu lo describes como 003F 1550 0460, o mas concretamente como d63 d5456 x460. A bajo nivel ese fichero es tan solo una descripcion de donde estan los demas, esto es tanto cat 6004 como asn1 6004 valen para obtener los IDs y los Path a los certificados.
    En concreto los paths son 3F00 6081 7004 3F00 6081 7005 y 3F00 6061 7006, que hay que girar para ir a tu notacion.

    Oye, muy interesante lo de los certificados de componente

  40. Comment por Paolo | 04/09/10 at 1:38 pm

    Madre mia, si esto la has hecho al poco de habértelo dado, no te quiero como enemigo, ni de coña jejej
    Muy muy bueno

  41. Comment por Fenix | 04/10/10 at 5:39 am

    En dos palabras: Im presionante

  42. dan
    Comment por dan | 04/10/10 at 8:26 am

    Bravo
    buen trabajo

  43. Comment por 10lopez10 | 04/12/10 at 7:39 am

    Te felicito, por fin has entrado donde no dejan entrar, ya ves bastante engorroso.
    Si lo pudieras cambiar, por lo menos en el DNI-e tuyo lo harias.
    Entonces no funciona

  44. Comment por Chipiron de Alcorcon | 04/13/10 at 1:51 am

    Buen trabajo para los que saben y para los que quieren aprender, felicidades por lo escrito.

  45. Comment por josejuan | 04/28/10 at 9:27 am

    Pedazo de artículo, si señor.

  46. Comment por nomad | 05/23/10 at 8:26 am

    uberh4ck p0wer …;-)

  47. Comment por snakingmax | 07/04/10 at 4:07 pm

    Muy buen aporte. Me ha encantado el artículo.

  48. Pep
    Comment por Pep | 09/08/10 at 1:08 pm

    Mucho has trabajado, pero te hubiera sido más sencillo estudiar el estandar internacional pkcs#15 y ver los comandos con «winscard apdu view utility». Por cierto, los id se ponen en HEX.

    Saludos

  49. Comment por jonsito | 09/16/10 at 11:18 am

    Magnífico artículo. pero no soy capaz de encontrar los enlaces al código…

    Pretos:
    Por favor, ¿puedes publicar el código que desarrollaste? ( o poner un enlace, o mandarmelo por correo a jonsito en terra punto es … )

    gracias a los dos 🙂

  50. Comment por RH202 | 12/10/10 at 12:09 pm

    Segun comenta Ruben en su twitter el XSS ya ha sido arreglado, se encontraba en:

Se han cerrado los comentarios