|
|
 |
ESTUDIO
COLECTIVO DE DESPROTECCIONES
Última Actualizacion:
25/10/2001
|
 |
|
Programa |
Access
97. Fase 2 |
W95 / W98 / NT |
Descripción |
Motor de bases de datos |
Tipo |
Aplicación comercial |
Tipo de Tutorial |
[X]Original, []Adaptación,
[]Aplicación, []Traducción |
Url |
http://www.microsoft.com |
Protección |
Contraseña
de acceso a bases de datos |
Dificultad |
1) Principiante, 2) Amateur,
3) Aficionado, 4) Profesional, 5) Especialista |
Herramientas |
SoftIce v3.25, W32dasm v8.9, UltraEdit
v6.10a ...etc |
Objetivo |
Simular
que las bases de datos no tienen contraseña |
Cracker |
Mr.Blue |
Grupo |
Whiskey
Kon Tekila |
Fecha |
16 de Octubre de 1999 |
INTRODUCCION |
Vamos a demostrar la "seguridad" de los datos
en Access 97. Todo lo que se va explicar ha sido probado sobre Access 97,
aunque sospecho que es válido para cualquier base de datos en formato
Access (al menos desde el Access 95). He dividido nuestro proyecto en varias
fases, cada una más difícil que la anterior, pero todas muy
fáciles. Ya veréis.
-
Parchear una base de datos para eliminar la contraseña de acceso.
-
Parchear el Access 97 para poder abrir bases de datos protegidas con contraseña
de acceso (sin saberla, claro).
Diseño de un gestor de contraseñas para bases de datos
de Access que permita descubrir la contraseña de una base de datos,
modificarla o borrarla.
|
RESUMEN
DE LO ANTERIOR |
En la primera parte
comenzamos por identificar el mecanismo utilizado por Acess97 para almacenar
las contraseñas de acceso a bases de datos. Descubrimos en que lugar
exacto de los ficheros .mdb se almacenan dichas contraseñas, así
como una forma de eliminar dichas contraseñas. Ya disponemos de
una herramienta para poder acceder a cualquier base de datos protegida
con contraseña de acceso, eliminándola. A partir de aquí,
vamos a llegar más lejos. Con ayuda del SoftIce vamos a entrar de
lleno en el código del Access 97, y vamos a intentar descifrar el
algoritmo de encriptación y desencriptación de la contraseña
de acceso para construir una aplicación capaz de leer y modificar
dicha contraseña. En esta fase nos vamos a limitar a descubrir en
dónde se realiza la comprobación de la contraseña
introducida por el usuario y la almacenada en la base de datos. |
LOS
PREPARATIVOS |
Pues manos a la obra. Para localizar dónde
se encuentran las rutinas de desencriptación que buscamos, se nos
pueden ocurrir muchas formas. Voy a utilizar la que considero más
lógica y directa.
El proceso es bien sencillo. En primer lugar, el Access debe cargar
la base de datos para decidir si debe pedir contraseña de acceso
o no. Para llevar a cabo esta decisión, puede que tenga que realizar
la desencriptación que buscamos. Aquí es donde vamos a atacar.
Si pide contraseña, debe comparar la introducida con la almacenada
en el fichero, por lo que esta vez no tendrá más remedio
que desencriptar la parte que contiene la contraseña, si no lo ha
hecho antes. Si no hemos tenido éxito atacando a la rutina que dice
si la base de datos tiene contraseña o no, atacaremos a la rutina
que realiza la comparación.
La rutina de desencriptación, lógicamente accederá
a una zona de memoria donde previamente el programa ha cargado el fichero
de la base de datos. En el SoftIce, crearemos un punto de ruptura que salte
cuando la aplicación acceda a las posiciones de memoria donde el
Access ha cargado el fichero.
Un poco de "API-culturilla"
Antes de comenzar poniendo puntos de ruptura a diestro y siniestro,
vamos a consultar la ayuda de la API de Windows. Vamos a conocer las funciones
CreateFile
y ReadFile. Los que ya estén familiarizados con ellas, pueden
saltarse esta parte.
En Windows, para leer un fichero se utiliza la función ReadFile,
con los siguientes parámetros:
BOOL ReadFile( |
|
HANDLE hFile, |
// handle del fichero a leer |
LPVOID lpBuffer, |
// puntero buffer que recibe los datos |
DWORD nNumberOfBytesToRead, |
// número de bytes a leer |
LPDWORD lpNumberOfBytesRead, |
// puntero al número de bytes leídos |
LPOVERLAPPED lpOverlapped ); |
// puntero a estructura OVERLAPPED |
El parámetro 'hFile' es el handle (que no es más que
un número utilizado por Windows para referenciar a un determinado
objeto) devuelto por la función CreateFile cuando se abrió
el fichero. Para no confundirnos con otra posible lectura a un fichero
distinto, tenemos que saber cuál es el handle que referencia
a la base de datos que estamos abriendo para poder colocar un punto de
ruptura en ReadFile que salte solo cuando sea invocado por Access
para leer nuestra base de datos. Por ello, tendremos que colocar otro punto
de ruptura en la llamada función CreateFile que abre nuestra
base de datos, para ver qué handle devuelve.
Otro parámetro de interés para nosotros es en qué
posición de memoria se van a guardar los bytes que se lean de dicho
fichero. Este parámetro es 'lpBuffer', que contiene la dirección
de memoria a partir de la cual se van a cargar los bytes que se lean del
fichero apuntado por 'hFile'.
El número de bytes que se quieren leer es 'nNumberOfBytesToRead'
y el que en realidad ha leído ReadFile después de
ejecutarse lo devuelve en la dirección apuntada por 'lpNumberOfBytesRead'.
Para abrir un fichero se utiliza CreateFile:
HANDLE CreateFile( |
|
LPCTSTR lpFileName, |
// dirección del nombre del fichero |
DWORD dwDesiredAccess, |
// modo acceso (lectura/escritura) |
DWORD dwShareMode, |
// modo compartido |
LPSECURITY_ATTRIBUTES lpSecurityAttributes, |
//descriptor seguridad |
DWORD dwCreationDistribution, |
// acción |
DWORD dwFlagsAndAttributes, |
// attributos |
HANDLE hTemplateFile ); |
// handle modelo para atributos |
Para nuestros "nobles" propósitos, tan sólo nos interesa
el primer parámetro, 'lpFileName', que es un puntero a una cadena
terminada en carácter cero que indica el nombre completo del fichero
que se desea abrir.
Y otro poco de "ASM-culturilla"
Todo esto está muy bien pero ¿cómo se come esto
en ensamblador? Y más concretamente ¿cómo se pasan
parámetros en llamadas a funciones en ensamblador?
El paso de parámetros se realiza mediante la pila. Antes de la
llamada, los parámetros se introducen en la pila mediante instrucciones
'PUSH' en orden inverso. Así, por ejemplo, una llamada a una función
declarada así:
Funcion (par1,par2,par3)
se realiza así:
....... |
Push parámetro3 |
Push parámetro2 |
Push parámetro1 |
Call Funcion |
Cmp Eax, 0 |
......... |
En la primera instrucción de Funcion, la pila estará
así:
(Esp) |
Dirección de retorno. (Cmp Eax, 0) |
(Esp+4) |
parámetro1 |
(Esp+8) |
parámetro2 |
(Esp+c) |
parámetro3 |
Así, por ejemplo, si ponemos un punto de ruptura en la función
CreateFile,
cuando salte, el contenido de la posición 'Esp+4' será el
parámetro 'lpFileName'.
Por otra parte, las funciones que devuelvan un valor, lo hacen a través
del registro 'Eax'. |
LA
CAZA |
Pues empezamos. Tal y como se dijo al principio,
colocaremos un punto de ruptura en las posiciones donde se almacene el
fichero abierto por el Access, para cazar las rutinas de desencriptación
cuando comiencen a funcionar. Para saber en qué parte de la memoria
se ha cargado la base de datos, tendremos que colocar otro punto de ruptura
en la función ReadFile y ver el contenido del parámetro
'lpBuffer'. El problema es que el único parámetro que nos
indica qué fichero es el que se está leyendo es 'hFile',
que a priori, no sabemos cual será el que se asignará a la
base de datos que estamos intentando abrir. Tendremos que poner otro punto
de ruptura en CreateFile y apuntar el puntero al fichero abierto
que devuelve.
Manos a la obra. Abrimos el Access, elegimos una de nuestras bases de
datos con contraseña, por ejemplo 'bd2.mdb' con contraseña
"WKT!". Antes de pulsar el botón de 'Abrir', pulsamos Ctrl+D y colocamos
un punto de ruptura en CreateFile:
bpx CreateFileA
Volvemos al Access con Ctrl+D y pulsamos el botón de 'Abrir'.
Salta el SoftIce y estamos en la primera instrucción de CreateFile.
Nos aseguramos que se está abriendo nuestra base de datos examinando
la cadena a la que debe apuntar del primer parámetro, 'lpFileName',
para ello escribimos:
d*(esp+4)
Si no tenéis ninguna otra aplicación, funcionando en paralelo
con el Access, esta llamada será la que buscamos. En caso contrario,
puede que esta otra aplicación ser la que está intentando
abrir otro fichero distinto, por lo que tendremos que ir pulsando Ctrl+D
hasta encontrar la llamada a CreateFile que abre nuestra 'bd2.mdb'.
Una vez localizada, examinamos el puntero al fichero que devuelve en
el registro 'Eax'. Para esto, pulsaremos una vez F11. Tras el F11, salimos
a:
4035A0A mov [esi],eax
pertenenciente a 'MsJet35'. El Access está utilizando la librería
'MsJet35.dll', que se encuentra en 'Windows\System'. Anotamos el valor
del registro 'eax', que en mi caso es 29 (en hexadecimal).
Ahora colocamos un punto de ruptura en ReadFile, para descubrir
en dónde se está cargando el fichero:
bpx ReadFile if *(esp+4)==29
El punto de ruptura es condicional, para que salte solo cuando el parámetro
'hFile' (primer parámetro de ReadFile) sea el asociado con
nuestra base de datos.
Pulsamos Ctrl+D y aparecemos en la primera instrucción de ReadFile.
Mostramos la zona de memoria donde se va a almacenar el fichero, que viene
marcada por el segundo parámetro, 'lpBuffer', de la función.
Escribimos:
d*(esp+8)
Imaginemos que esto nos muestra el contenido de la memoria a partir
de la dirección 1864000 (por ejemplo).
Pulsamos F11, y en la zona de memoria apuntada debe aparecer el inicio
del fichero. Este contiene, a partir del quinto byte, la cadena 'Standard
Jet DB'.
Colocamos un punto de ruptura en los accesos a la dirección donde
sabemos que comienza la contraseña encriptada, que en mi caso será
en:
1864000 + 42 = 1864042
El punto de ruptura quedará así:
bpm 1864042 rw
Pulsamos Ctrl+D, y volvemos a aparecer al inicio de ReadFile.
Se va a volver a intentar la lectura del fichero. Deshabilitamos el 'bpm'
anterior y miramos donde se va a almacenar esta nueva lectura. En mi caso,
sale que se va a escribir en el mismo sitio de antes. Pulsamos F11 y como
no ha cambiado la zona donde se carga el fichero, volvemos a habilitar
el 'bpm' en el mismo sitio. Si la carga se hace en un sitio distinto, tendremos
que colocar el 'bpm' en el sitio correspondiente.
Pulsamos Ctrl+D y aparecemos en la dirección 4035793:
:0403578F |
02D1 |
add dl, cl |
:04035791 |
8A2F |
mov ch, byte ptr [edi] |
:04035793 |
47 |
inc edi |
:04035794 |
8A0416 |
mov al, byte ptr [esi+edx] |
:04035797 |
880C16 |
mov byte ptr [esi+edx], cl |
:0403579A |
88041E |
mov byte ptr [esi+ebx], al |
:0403579D |
02C1 |
add al, cl |
:0403579F |
FEC3 |
inc bl |
:040357A1 |
322C06 |
xor ch, byte ptr [esi+eax] |
:040357A4 |
4D |
dec ebp |
:040357A5 |
886FFF |
mov byte ptr [edi-01], ch |
:040357A8 |
8A0C1E |
mov cl, byte ptr [esi+ebx] |
:040357AB |
75E2 |
jne 0403578F |
:040357AD |
5D |
pop ebp |
En la instrucción anterior a ésta, se ha cargado el primer
carácter de la contraseña encriptada en el registro CH. A
partir de aquí vamos pulsando F8 y vemos como se va desencriptando
la contraseña, apareciendo finalmente la nuestra "WKT!". Lo que
tenemos aquí, es parte de la rutina de desencriptación, que
ya analizaremos en la siguiente fase del proyecto.
Con esto, ya somos capaces de conocer la contraseña de cualquier
base de datos, aunque eso sí, con la ayuda del SoftIce. Borramos
el 'bpm' anterior y creamos un punto de ruptura en los accesos a las posiciones
ocupadas por la contraseña desencriptada, en mi caso:
bpr 1864042 1864045 rw
Pulsamos Ctrl+D y aparecemos en 4036028:
:04036020 |
8D7542 |
lea esi, dword ptr [ebp+42] |
:04036023 |
E840F7FFFF |
call 04035768 |
:04036028 |
803E00 |
cmp byte ptr [esi], 00 |
:0403602B |
0F85E4680500 |
jne 0408C915 |
:04036031 |
837D3E00 |
cmp dword ptr [ebp+3E], 00000000 |
:04036035 |
752A |
jne 04036061 |
En esa intrucción, estamos comparando el primer carácter
de la contraseña con el carácter cero.
Es el momento de parar un momento y recordar dónde estamos y
a dónde vamos ;-)
Tal y como dijimos anteriormente, Access debe comprobar inicialmente
al abrir una base de datos si ésta tiene contraseña de acceso
o no. Si la tiene, debe interrumpir la apertura de la base de datos y pedírsela
al usuario. Puede que una base de datos sin contraseña, tenga almacenada
como contraseña una cadena de caracteres de ASCII 0. Para comprobar
esto, podemos borrar todos los puntos de rupturas, colocar uno de ejecución
en 403628 ('bpx 403628'), volver al Access e intentar abrir una base de
datos sin contraseña de acceso. Efectivamente, al saltar el punto
de ruptura, si vemos el contenido de la zona apuntada por el registro ESI,
donde antes aparecía "WKT!", ahora aparecen un montón de
ceros.
Hemos descubierto el significado de la cadena que utilizábamos
en la fase anterior para parchear las bases de datos. No es más
que una cadena de 14 caracteres de ASCII 0. El parche consistirá
en hacer creer al Access, que la contraseña de la base de datos
que está abriendo es una cadena de ASCII 0, o lo que es lo mismo,
que la base de datos que se está abriendo no tiene contraseña.
El objetivo final del parche sobre 'Msjet35.dll' será que la ejecución,
al llegar a 4036028 continue por 4036031, sin saltar a 408C915. Comprobamos
si tenemos razón, con el SoftIce. Usando el mismo punto de ruptura
de antes, e intentamos abrir una base de datos con contraseña. Cuando
volvamos al SoftIce, escribimos el registro EIP con la dirección
4036031:
reip 4036031
Se abre la base de datos sin más problemas. Ya hemos cazado a
la "liebre". |
REMATANDO
A LA PRESA |
Ahora bien, ¿qué posibilidades
se nos ocurren para hacer el parche?. La primera que se nos ocurre es "nopear"
los 6 bytes del salto en 403602B. Es lo más sencillo, escribir 6
NOP's, pero poco elegante. Además, esta técnica debe evitarse
en todo lo posible ya que, puede que más adelante, la aplicación
repita la comprobación (aunque esto no ocurre en el caso que nos
ocupa, y "nopear" sería una solución válida), por
lo que tendríamos que "currarnos" el crack un poco más, y
parchear en más sitios.
La solución ideal sería que la aplicación, si más
adelante vuelve a realizar la comprobación que hemos "petado", el
resultado sea el mismo que el logrado al "petar" la primera comprobación.
En nuestro caso, lo que nos interesa es que la aplicación encuentre
una cadena de caracteres de ASCII 0 donde debería encontrarse la
contraseña desencriptada.
Formas de hacerlo:
-
Modificar la rutina que realiza la desencriptación para que escriba
solo ceros.
-
Justo despues de ejecutar la rutina de desencriptación, sobreescribir
con ceros los valores escritos por dicha rutina, mediante código
que nosostros añadimos.
Veamos la primera opción. Si la llevamos a cabo, la rutina de desencriptación
no funcionará adecuadamente y ¿qué ocurre si esa rutina
es utilizada para más cosas, aparte de desencriptar la contraseña?
Como hemos visto al tracear la rutina, ésta desencripta algo más
aparte de las catorce posiciones de la contraseña. Además,
si usamos el W32Dasm para desensamblar 'MsJet35.dll', la rutina de desencriptación
que comienza en 4035768, es llamada desde muchos otros sitios, aparte de
la llamada en 4036023.
Desechada la primera opción por "peligrosa", nos centramos en
la segunda. Tenemos que añadir código, o bien sobreescribiendo
parte del existente si tenemos sitio, buscando una zona de código
"sobrante" a donde poder saltar y meter nuestra rutina, o bien haciéndonos
sitio modificando la cabecera PE. ¿ Cuántos bytes vamos a
necesitar? Dependerá de lo que queramos hacer y de nuestra calidad
como "coders".
Vamos a hacer una solución intermedia. No escribiremos los 14
bytes de la contraseña a cero, solo los cuatro primeros y después
dejaremos la misma comparación:
:04036020 |
8D7542 |
lea esi, dword ptr [ebp+42] |
:04036023 |
E840F7FFFF |
call 04035768 |
:04036028 |
C70600000000 |
mov dword ptr [esi], 00000000 |
:0403602E |
803E00 |
cmp byte ptr [esi], 00 |
:04036031 |
837D3E00 |
cmp dword ptr [ebp+3E], 00000000 |
:04036035 |
752A |
jne 04036061 |
La probamos con el SoftIce y ... perfecto.
Abrimos el editor hexadecimal y buscamos la cadena "80 3E 00 0F 85 E4
68 05 00" en el fichero 'MsJet35.dll' del directorio 'Windows\System'.
Esta cadena aparece a partir del offset 35628h. Sustituímos por
"C7 06 00 00 00 00 80 3E 00" y probamos.
El parcheador se realiza exactamente igual que en la fase anterior aunque
en este caso es hasta más sencillo porque ya sabemos qué
fichero es nuestro objetivo, 'MsJet35.dll'. Para saber cuál es el
directorio del sistema, donde estará 'MsJet35.dll' podemos usar
la función de la API GetSystemDirectory.
.... era una raza muy atrasada .... |
.... al borde del siglo XXI aún utilizaban aplicaciones Micro$oft
... |
|
[
Entrada
|
Documentos
Genéricos
| WkT!
Web Site ] |
[
Todo
el ECD | x Tipo de Protección
| x
Fecha de Publicación | x Orden Alfabético
] |
|
(c)
Whiskey Kon Tekila [WkT!] - The Original Spanish Reversers.
Si necesitas
contactar con nosotros,
leeestoantes e infórmate
de cómo puedes ayudarnos |
|
|
|
|
|