Jorge Alberto Mussuto Sr.

Jorge Alberto Mussuto Sr.
Somewhere in Massachusetts ®

Wednesday, April 14, 2010

Análisis de la estructura interna del DNI electrónico


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: 0×6f 0×0c 0×85 0×0a 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: 0×6f 0×0d 0×85 0×0b 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: 0×6f 0×0c 0×85 0×0a 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: 0×6f 0×0c 0×85 0×0a 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: 0×6f 0×18 0×84 0×0a 0×49 0×43 0×43 0×2e 0×43 0×72 0×79 0×70 0×74 0×6f 0×85 0×0a ?o???ICC.Crypto? 0×38 0×3f 0×11 0×00 0×0a 0xff 0xff 0xff 0xff 0xff 0×90 0×00 to??8???????
File id = 5439 GET_RESPONSE: 0×6f 0×14 0×84 0×06 0×49 0×43 0×43 0×2e 0×49 0×44 0×85 0×0a 0×38 0×3f 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: 0×6f 0×1a 0×84 0×0c 0xa0 0×00 0×00 0×00 0×63 0×50 0×4b 0×43 0×53 0×2d 0×31 0×35 ?o???????cPKCS-1 0×85 0×0a 0×38 0×50 0×15 0×00 0×0c 0xff 0xff 0xff 0xff 0xff 0×90 0×00 15??8P????????
File id = 12640 GET_RESPONSE: 0×6f 0×18 0×84 0×0a 0×44 0×4e 0×49 0×65 0×2e 0×41 0×64 0×6d 0×69 0×6e 0×85 0×0a ?o???DNIe.Admin? 0×38 0×60 0×31 0×00 0×0a 0xff 0xff 0xff 0xff 0xff 0×90 0×00 in??8`1?????
 File id = 24928 GET_RESPONSE: 0×6f 0×16 0×84 0×08 0×44 0×4e 0×49 0×65 0×2e 0×50 0×75 0×62 0×85 0×0a 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: 0×6f 0×17 0×84 0×09 0×44 0×4e 0×49 0×65 0×2e 0×50 0×72 0×69 0×76 0×85 0×0a 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 0×3f:
File name = Master.File  GET_RESPONSE:  0×6f 0×19 0×84 0×0b 0×4d 0×61 0×73 0×74 0×65 0×72 0×2e 0×46 0×69 0×6c 0×65 0×85 ?o???Master.File 0×0a 0×38 0×3f 0×00 0×00 0×0b 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: 0×6f 0×0c 0×85 0×0a 0×01 0×2f 0×03 0×00 0×28 0×00 0×80 0xff 0xff 0xff 0×90 0×00 ?o????/??(?????? READ_BINARY: 0×44 0×4e 0×49 0×65 0×20 0×30 0×31 0×2e 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 0×2d 0×28 0×28 0×34 0×2e H 4C34 EXP 1-((40×32 0×2d 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: 0×6f 0×0c 0×85 0×0a 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: 0×6f 0×0c 0×85 0×0a 0×01 0×60 0×1f 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: 0×6f 0×0c 0×85 0×0a 0×01 0×60 0×20 0×04 0×2c 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: 0×6f 0×0c 0×85 0×0a 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<0×7e:
        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(0×1F0FFF, 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<0×7e:
              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]=="———<<<<<———-":
                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]=="———<<<<<———-":
            duplogs[nl][0]="———<<<<<———-"
            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==0×1A7F4BD:
          LastRecvLenPtr = GetRegValue("ECX") 
        if ea==0×1A7F4C9:
          LastRecvBuffer = GetRegValue("EDX")   
        if ea==0×1A5DF95: #ea==0×1A5E285 or
          arr.append("\\\\\\\\\\\\ProcessAndXorBlock\\\\\\\\\\\\")
          r = GetRegValue("EAX")
          LastProcessAndXorBlockInput=readmem(r,8)
          arr.append(LastProcessAndXorBlockInput)
          logs.append(arr)  
        if ea==0×1A5E02E or ea==0×1A5E03E: #ea==0×1A5E306 or ea==0×1A5E2F6 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==0×1A7F4CF:
          arr.append("———>>>COMMAND>>>———-")
          r1 = GetRegValue("EDI")
          r2 = GetRegValue("EBP")
          arr.append(readmem(r1,r2))
          logs.append(arr)  
        if ea==0×1A7F4D7:
          arr.append("———<<<<<———-")
          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(0×1A7F4CF,0,BPT_SOFT)
enable_bpt(0×1A7F4CF,True)

#CryptoPP_DES_Base_ProcessAndXorBlock (entrando)
add_bpt(0×1A5E285,0,BPT_SOFT)
enable_bpt(0×1A5E285,True)
#CryptoPP_DES_Base_ProcessAndXorBlock (saliendo)
add_bpt(0×1A5E306,0,BPT_SOFT)
enable_bpt(0×1A5E306,True)
#CryptoPP_DES_Base_ProcessAndXorBlock (saliendo)
add_bpt(0×1A5E2F6,0,BPT_SOFT)
enable_bpt(0×1A5E2F6,True)

#CryptoPP_DES_EDE2_Base_ProcessAndXorBlock (entrando)
add_bpt(0×1A5DF95,0,BPT_SOFT)
enable_bpt(0×1A5DF95,True)
#CryptoPP_DES_EDE2_Base_ProcessAndXorBlock (saliendo)
add_bpt(0×1A5E02E,0,BPT_SOFT)
enable_bpt(0×1A5E02E,True)
#CryptoPP_DES_EDE2_Base_ProcessAndXorBlock (saliendo)
add_bpt(0×1A5E03E,0,BPT_SOFT)
enable_bpt(0×1A5E03E,True)
#Keep last recv len ptr
add_bpt(0×1A7F4BD,0,BPT_SOFT)
enable_bpt(0×1A7F4BD,True)
#Keep last recv buffer
add_bpt(0×1A7F4C9,0,BPT_SOFT)
enable_bpt(0×1A7F4C9,True)
#Keep response
add_bpt(0×1A7F4D7,0,BPT_SOFT)
enable_bpt(0×1A7F4D7,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:
<>
Fichero Master.File -> DNIe.Priv -> 0×570:
<>
Fichero Master.File -> DNIe.Pub -> 0×670:
<>
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.

No comments:

Blog Archive

Quilts

Where am I?