En un artículo anterior hablaba de como recuperar las imágenes borradas de un disco, o tarjeta de memoria, examinando cluster a cluster en busca de las imágenes. Ese programa estaba pensado para utilizarlo directamente sobre un disco o sobre la imagen de un disco, no sirve para examinar otro tipo de archivos, pero usando un sistema parecido podríamos extraer imágenes de cualquier archivo. La única diferencia es que en vez de ir buscando cluster a cluster, tendríamos que ir buscando byte a byte. Este proceso es mucho mas lento así que no es el mas adecuado para examinar discos enteros, sin embargo es perfecto para explorar archivo de un tamaño normal.
El principio es el mismo de antes, buscamos la marca que indica el comienzo de un archivo jpeg, es decir, los bytes FFD8FF. Una vez encontrada, intentamos obtener, de los bytes que siguen a esos tres, una imagen jpeg, para luego continuar la búsqueda en el byte siguiente.
El programa necesita que se le pasen dos parámetros, el archivo de entrada y la plantilla que sirve para dar nombre a las imágenes recuperadas. La plantilla tiene que incluir "%d", esta variable sera sustituida por un valor que se va incrementando. Por ejemplo, si queremos guardar las imágenes en C:\Temp utilizaremos C:\Temp\%d.jpg
El código es el siguiente (es una aplicación de consola):
program JpegRecover; {$APPTYPE CONSOLE} uses SysUtils, Classes; procedure Recover(Origen: TStream; FormatStr: String; var Index: Integer); var Destino: TMemoryStream; Buffer: array[0..$FFFF] of Byte; Header: array[1..4] of Byte; Size: Integer; begin Destino:= TMemoryStream.Create; try // Escribimos los 2 primeros bytes del archivo Header[1]:= $FF; Header[2]:= $D8; Destino.WriteBuffer(Header,2); Origen.Seek(-1,soFromCurrent); // En este bucle vamos copiando segmento a segmento while TRUE do begin FillChar(Header,Sizeof(Header),#0); Origen.Read(Header,4); Size:= ((Header[3] shl 8) + Header[4]) - 2; case Header[2] of $01,$D0..$D8: begin // Estos segmentos solo miden dos bytes, // asi que escribimos 2 bytes y Destino.WriteBuffer(Header,2); // retrocedemos otros dos hacia atras. Origen.Seek(-2,soFromCurrent); end; $D9: begin // Este indica que llegamos al final del archivo jpeg Destino.WriteBuffer(Header,2); // Guardamos el archivo usando la plantilla Destino.SaveToFile(Format(FormatStr,[Index])); Writeln('Recuperada: ' + Format(FormatStr,[Index])); // Incrementamos el indice inc(Index); // Salimos del procedure Exit; end; $DA: begin // Este segmento no tiene tamaño Destino.WriteBuffer(Header,2); Origen.Seek(-2,soFromCurrent); // Buscamos el final byte a byte while TRUE do begin Origen.ReadBuffer(Buffer,1); // Posible comienzo de otro segmento if Buffer[0] = $FF then begin Origen.ReadBuffer(Buffer,1); // $FF00 = Secuencia de escape // $FFD0 .. $FFD7 = Se deben de ignorar if (Buffer[0] <> 0) and not (Buffer[0] in [$D0..$D7]) then begin // Si encontramos el comienzo de otro segmento, // retrocedemos dos bytes, y volvemos al bucle principal Origen.Seek(-2,soFromCurrent); break; end else begin // Si no es el comienzo de otro segmento, escribimos // y continuamos Header[1]:= $FF; Header[2]:= Buffer[0]; Destino.WriteBuffer(Header,2); end; end else Destino.WriteBuffer(Buffer,1); end; end else begin // Cualquier otro segmento lo copiamos directamente Origen.ReadBuffer(Buffer,Size); Destino.WriteBuffer(Header,4); Destino.WriteBuffer(Buffer,Size); end; end; end; finally Destino.Free; end; end; procedure Scanfile(Stream: TStream; FormatStr: String; var Index: Integer); var B: Byte; Leidos: Integer; Posicion: int64; begin // Vamos leyendo byte a byte el archivo Leidos:= Stream.Read(B,1); while Leidos = 1 do if B = $FF then begin Leidos:= Stream.Read(B,1); if Leidos = 1 then if B = $D8 then begin Leidos:= Stream.Read(B,1); if Leidos = 1 then if B = $FF then begin // Hasta encontrar la combinacion $FFD8FF Writeln('Encontrada una posible imagen.'); // Guardamos la posicion Posicion:= Stream.Position; try // Intentamos recuperar la imagen Recover(Stream,FormatStr,Index); except // Si se produce un error lo mostramos, pero no salimos del bucle On E: Exception do Writeln('Error: ' + E.Message); end; // Volvemos a la posicion en la que estabamos Stream.Position:= Posicion; end; end; end else Leidos:= Stream.Read(B,1); end; var i: Integer; Stream: TFileStream; begin Writeln('JpegRecover 0.1 - Copyright (c) 2007 Domingo Seoane'); Writeln('<a href="http://delphi.jmrds.com'">http://delphi.jmrds.com'</a>); Writeln; if ParamCount = 2 then try // Comprobamos que se pueden crear archivos en la ruta indicada TFileStream.Create(Format(ParamStr(2),[0]),fmCreate).Free; // Borramos el archivo de prueba DeleteFile(Format(ParamStr(2),[0])); i:= 0; // Creamos el Stream del archivo Stream:= TFileStream.Create(Paramstr(1),fmOpenRead); try Writeln('Explorando el archivo: ' + ParamStr(1)); Scanfile(Stream,ParamStr(2),i); finally Stream.Free; end; except // Si se produce un error, lo mostramos On E: Exception do Writeln('Error: ' + E.Message); end; end.
Por ejemplo, para buscar imágenes en el archivo "ejemplo.dat" y guardarlas en C:\Temp, usaríamos el siguiente comando:
JpegRecover ejemplo.dat C:\Temp\%d.jpg
El código completo y programa, los puedes bajar de aquí
Ahora le podemos buscar un uso mas "ludico" a este codigo, y por ejemplo fisgar un poco en los archivos "thumb.db". Estos archivos "thumb.db" (al parecer en Vista se llaman thumbcache_xxxx.db) son creados por el explorer cuando vemos una carpeta con imágenes utilizando las "Vistas en miniatura". Lo "divertido" es que tiene el mal habito de no borrar las miniaturas almacenadas en la cache cuando se borra la imagen original, así que podemos encontrarnos con alguna sorpresa.
Es una aplicación de consola:
program Fisgon; {$APPTYPE CONSOLE} uses SysUtils, Classes; procedure Recover(Origen: TStream; FormatStr: String; var Index: Integer); var Destino: TMemoryStream; Buffer: array[0..$FFFF] of Byte; Header: array[1..4] of Byte; Size: Integer; begin Destino:= TMemoryStream.Create; try // Escribimos los 2 primeros bytes del archivo Header[1]:= $FF; Header[2]:= $D8; Destino.WriteBuffer(Header,2); Origen.Seek(-1,soFromCurrent); // En este bucle vamos copiando segmento a segmento while TRUE do begin FillChar(Header,Sizeof(Header),#0); Origen.Read(Header,4); Size:= ((Header[3] shl 8) + Header[4]) - 2; case Header[2] of $01,$D0..$D8: begin // Estos segmentos solo miden dos bytes, // asi que escribimos 2 bytes y Destino.WriteBuffer(Header,2); // retrocedemos otros dos hacia atras. Origen.Seek(-2,soFromCurrent); end; $D9: begin // Este indica que llegamos al final del archivo jpeg Destino.WriteBuffer(Header,2); // Guardamos el archivo usando la plantilla Destino.SaveToFile(Format(FormatStr,[Index])); Writeln('Recuperada: ' + Format(FormatStr,[Index])); // Incrementamos el indice inc(Index); // Salimos del procedure Exit; end; $DA: begin // Este segmento no tiene tamaño Destino.WriteBuffer(Header,2); Origen.Seek(-2,soFromCurrent); // Buscamos el final byte a byte while TRUE do begin Origen.ReadBuffer(Buffer,1); // Posible comienzo de otro segmento if Buffer[0] = $FF then begin Origen.ReadBuffer(Buffer,1); // $FF00 = Secuencia de escape // $FFD0 .. $FFD7 = Se deben de ignorar if (Buffer[0] <> 0) and not (Buffer[0] in [$D0..$D7]) then begin // Si encontramos el comienzo de otro segmento, // retrocedemos dos bytes, y volvemos al bucle principal Origen.Seek(-2,soFromCurrent); break; end else begin // Si no es el comienzo de otro segmento, escribimos // y continuamos Header[1]:= $FF; Header[2]:= Buffer[0]; Destino.WriteBuffer(Header,2); end; end else Destino.WriteBuffer(Buffer,1); end; end else begin // Cualquier otro segmento lo copiamos directamente Origen.ReadBuffer(Buffer,Size); Destino.WriteBuffer(Header,4); Destino.WriteBuffer(Buffer,Size); end; end; end; finally Destino.Free; end; end; procedure Scanfile(Stream: TStream; FormatStr: String; var Index: Integer); var B: Byte; Leidos: Integer; Posicion: int64; begin // Vamos leyendo byte a byte el archivo Leidos:= Stream.Read(B,1); while Leidos = 1 do if B = $FF then begin Leidos:= Stream.Read(B,1); if Leidos = 1 then if B = $D8 then begin Leidos:= Stream.Read(B,1); if Leidos = 1 then if B = $FF then begin // Hasta encontrar la combinacion $FFD8FF Writeln('Encontrada una posible imagen.'); // Guardamos la posicion Posicion:= Stream.Position; try // Intentamos recuperar la imagen Recover(Stream,FormatStr,Index); except // Si se produce un error lo mostramos, pero no salimos del bucle On E: Exception do Writeln('Error: ' + E.Message); end; // Volvemos a la posicion en la que estabamos Stream.Position:= Posicion; end; end; end else Leidos:= Stream.Read(B,1); end; procedure Buscar(Path: string; var Index: Integer); var SR: TSearchRec; Stream: TFileStream; begin if Path = '' then exit; if copy(Path, Length(Path), 1) <> '\' then Path := Path + '\'; if FindFirst(Path + '*', faDirectory, SR) = 0 then repeat if (SR.Name <> '.') and (SR.Name <> '..') then Buscar(Path + SR.Name, Index); until FindNext(SR) <> 0; FindClose(SR); if FindFirst(Path + 'thumb*.db',faAnyfile, SR) = 0 then repeat Stream:= TFileStream.Create(Path + SR.Name,fmOpenRead); try Writeln('Explorando el archivo: ' + Path + SR.Name); Scanfile(Stream,ParamStr(2),Index); finally Stream.Free; end; until FindNext(SR) <> 0; FindClose(SR); end; var i: Integer; begin if ParamCount = 2 then try // Comprobamos que se pueden crear archivos en la ruta indicada TFileStream.Create(Format(ParamStr(2),[0]),fmCreate).Free; // Borramos el archivo de prueba DeleteFile(Format(ParamStr(2),[0])); // Buscamos en la ruta indicada i:= 0; Buscar(ParamStr(1),i); except // Si se produce un error, lo mostramos On E: Exception do Writeln('Error: ' + E.Message); end; end.
Por ejemplo:
Fisgon C:\ %d.jpg
Bueno, creo que con esto ya hay bastante para entretenerse durante un tiempo. Pero si con esto no es suficiente, también puedes usar este mismo código para mirar dentro de los propios archivos jpeg. Alguno de ellos además de la imagen que muestran, incluyen una miniatura que se utiliza a modo de "vista previa". Y lo curioso en este caso, es que algunos programas al modificar la imagen no cambian la miniatura. Así nos podemos encontrar con fotos recortadas o con partes borradas, y sin embargo una miniatura de la imagen sin manipular.