Random IRC quote :      <_infi_> hahaha <_infi_> LOL <_infi_> sisi

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 0×85 0x0a 0×01 0×00 0×05 0×00 0×32 0×80 0xff 0xff 0xff 0xff 0×90 0×00 ?o???????2??????

File id = 4608 GET_RESPONSE: 0x6f 0x0d 0×85 0x0b 0×15 0×00 0×12 0×00 0×21 0xff 0xff 0xff 0xff 0xff 0×02 0×90 ?o???????!??????

File id = 8448  GET_RESPONSE: 0x6f 0x0c 0×85 0x0a 0×25 0×00 0×21 0×02 0×58 0xb1 0xff 0×80 0xd1 0xff 0×90 0×00 ?o???%?!?X??????

File id = 41312 GET_RESPONSE: 0x6f 0x0c 0×85 0x0a 0×01 0×60 0xa1 0×00 0×20 0×80 0×80 0xff 0xff 0xff 0×90 0×00 ?o????`?? ??????

Los siguientes son DFs encontrados:

File id = 4415 GET_RESPONSE: 0x6f 0×18 0×84 0x0a 0×49 0×43 0×43 0x2e 0×43 0×72 0×79 0×70 0×74 0x6f 0×85 0x0a ?o???ICC.Crypto? 0×38 0x3f 0×11 0×00 0x0a 0xff 0xff 0xff 0xff 0xff 0×90 0×00 to??8???????

File id = 5439 GET_RESPONSE: 0x6f 0×14 0×84 0×06 0×49 0×43 0×43 0x2e 0×49 0×44 0×85 0x0a 0×38 0x3f 0×15 0×00 ?o???ICC.ID??8??0×06 0xff 0xff 0xff 0xff 0xff 0×90 0×00 ID??8???

File id = 5456 GET_RESPONSE: 0x6f 0x1a 0×84 0x0c 0xa0 0×00 0×00 0×00 0×63 0×50 0x4b 0×43 0×53 0x2d 0×31 0×35 ?o???????cPKCS-1 0×85 0x0a 0×38 0×50 0×15 0×00 0x0c 0xff 0xff 0xff 0xff 0xff 0×90 0×00 15??8P????????

File id = 12640 GET_RESPONSE: 0x6f 0×18 0×84 0x0a 0×44 0x4e 0×49 0×65 0x2e 0×41 0×64 0x6d 0×69 0x6e 0×85 0x0a ?o???DNIe.Admin? 0×38 0×60 0×31 0×00 0x0a 0xff 0xff 0xff 0xff 0xff 0×90 0×00 in??8`1?????

 File id = 24928 GET_RESPONSE: 0x6f 0×16 0×84 0×08 0×44 0x4e 0×49 0×65 0x2e 0×50 0×75 0×62 0×85 0x0a 0×38 0×60 ?o???DNIe.Pub??8 0×61 0×00 0×08 0xe0 0xff 0xff 0xff 0xff 0×90 0×00 ub??8`a???

File id = 33120 GET_RESPONSE: 0x6f 0×17 0×84 0×09 0×44 0x4e 0×49 0×65 0x2e 0×50 0×72 0×69 0×76 0×85 0x0a 0×38 ?o???DNIe.Priv?? 0×60 0×81 0×00 0×09 0xe0 0xff 0xff 0xff 0xff 0×90 0×00 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 0×19 0×84 0x0b 0x4d 0×61 0×73 0×74 0×65 0×72 0x2e 0×46 0×69 0x6c 0×65 0×85 ?o???Master.File 0x0a 0×38 0x3f 0×00 0×00 0x0b 0xff 0xff 0xff 0xff 0xff 0×90 0×00 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 0×85 0x0a 0×01 0x2f 0×03 0×00 0×28 0×00 0×80 0xff 0xff 0xff 0×90 0×00 ?o????/??(?????? READ_BINARY: 0×44 0x4e 0×49 0×65 0×20 0×30 0×31 0x2e 0×31 0×33 0×20 0×41 0×31 0×31 0×20 0×48 ?DNIe 01.13 A11 0×20 0×34 0×43 0×33 0×34 0×20 0×45 0×58 0×50 0×20 0×31 0x2d 0×28 0×28 0×34 0x2e H 4C34 EXP 1-((40×32 0x2d 0×35 0×29 0×29 0×00 0×00 0×00 0×90 0×00 1-((4.2-5)

File id = 1536 GET_RESPONSE: 0x6f 0x0c 0×85 0x0a 0×01 0×00 0×06 0×00 0×19 0×00 0xff 0xff 0xff 0xff 0×90 0×00 ?o??????????????READ_BINARY: El IDESP. Varía entre cada DNI.

File id = 8032 GET_RESPONSE: 0x6f 0x0c 0×85 0x0a 0×01 0×60 0x1f 0×03 0×25 0×00 0xff 0xff 0xff 0xff 0×90 0×00 ?o????`??%??????READ_BINARY: Certificado. Varía entre cada DNI.

File id = 8288 GET_RESPONSE: 0x6f 0x0c 0×85 0x0a 0×01 0×60 0×20 0×04 0x2c 0×00 0xff 0xff 0xff 0xff 0×90 0×00 ?o????` ?,??????READ_BINARY: 

(Convertido con openssl) 

Certificate:Data:

Version: 3 (0×2)

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 (0×10001)

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 0×85 0x0a 0×01 0×60 0xa8 0×00 0xb5 0×00 0xff 0xff 0xff 0xff 0×90 0×00 ?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>=0×20 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>=0×20 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 -> 0×160:  

.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 -> 0×460

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 -> 0×470:

<<Bloque de datos sin interpretar>>

Fichero Master.File -> DNIe.Priv -> 0×570:

<<Bloque de datos sin interpretar>>

Fichero Master.File -> DNIe.Pub -> 0×670:

<<Bloque de datos sin interpretar>>

Los ficheros 0×470, 0×570 que cuelgan de DNIe.Priv, y el fichero 0×670, 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.

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

  1. Comentario por Karcrack | 03/16/10 at 10:03 am

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

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

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

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

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

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

    Muy bueno Javi, me ha gustado :-)

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

    Muy bueno tío!!

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

    Está de puta madre Javi, enhorabuena :)

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

    Muy bueno!!! buen trabajo

  8. Comentario 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. Comentario 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. Comentario por Seifreed | 03/16/10 at 3:54 pm

    Hola!

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

    Un saludo

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

    Que nivel! :-)

  12. Comentario 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
    Comentario 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. Comentario 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. Comentario 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. Comentario 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. Comentario 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
    Comentario 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. Comentario 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. Comentario por juanpines | 03/21/10 at 6:59 pm

    Muy bueno el articulo.

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

    Impresionante tío…
    Me q

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

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

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

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

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

    Muy bueno! Gracias por compartirlo!

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

    ¡Eres mi nuevo dios!

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

    excelente trabajo…

  27. Comentario 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. Comentario por Ivan Camara | 03/22/10 at 5:23 am

    Muy bueno, gracias por compartir.

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

    Bravo, un trabajo espectacular.

    Mil gracias por compartir ;-)

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

    Se agradece la información. Mil gracias.

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

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

    Saludos!

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

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

    Saludos!

  33. Comentario 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. Comentario por Maktutote | 03/22/10 at 2:52 pm

    Impresionante!!!

    Da gusto leer artículos así :D

  35. Comentario 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 0×6004 (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. Comentario 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. Comentario por Alejandro Rivero | 03/24/10 at 5:49 pm

    Y oye chico, no veo que el 0×6004 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. Comentario 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 0×6004, que es simplemente el CFD) a ver su se anima a ponerlo por algun sitio

  39. Comentario 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. Comentario 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. Comentario por Fenix | 04/10/10 at 5:39 am

    En dos palabras: Im presionante

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

    Bravo
    buen trabajo

  43. Comentario 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. Comentario 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. Comentario por josejuan | 04/28/10 at 9:27 am

    Pedazo de artículo, si señor.

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

    uberh4ck p0wer …;-)

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

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

Dejar un comentario »»