2013/07/27

Mutation Testing Framework


Presento aquí el resultado de mi trabajo colectivo de mutación de código para testing. Digo "colectivo" por las horas que invertí en análisis y diseño viajando en colectivo[1]. Seguramente estoy reinventando la rueda[2], pero no me importa, el camino avasalla en importancia al destino.

If you need an english version, ask me and I will add it to the top of my backlog

He utilizado bastante TDD pues de no ser así hubiese incurrido en pecado mortal. Hay que predicar con el ejemplo. Es más, el proceso fue reflexivo, se aplicó contra su mismo codigo fuente. Con respecto a este tema, me interesa aclarar que para mi TDD es tan sólo una técnica más, un recurso en nuestra valijita de herramientas. No significa que uno se sienta con la mente en blanco y tira un test y el código y así avanza. Me parece correcto hacerlo así en un taller de TDD, pero si nos detenemos ahí, es como quedarse con la idea de que la danza clásica son las posiciones de los pies y agarrarse de una barra frente a un espejo, que tocar el piano son unas escalas, boxear es saltar la soga y perseguir gallinas o programar es implementar sort() y search().

Mi idea original fue utilizar php que tiene un tokenizador[3] y es lo que más conozco, para mi desgracia. Cuando en los primeros viajes comencé a percibir que podía haber mucha recursividad pensé en erlang, el cual necesito practicar para recupera mi autoestima. Inmediatamente rechacé la idea, KISS[4]. Dos viajes después, tras haber visto que python tambien tiene un sencillo tokenizador[5] (y quizas ruby[6] y cobol[7]) y percibiendo el potencial de utilizar multicores en múltiples máquinas, había reincorporado erlang y en lugar de un mutador de código para php tenía en mente un framework para usar con cualquier lenguaje que alguien se tomara la molestia en preparar. Bueno, ese era el plan.

Testing por mutación


El testing por mutación de código se basa en la idea de que si los test están bien hechos, deberían detectar cualquier cambio en el código fuente. Entonces, hay que modificar arbitrariamente el código respetando las reglas del lenguaje y ejecutar los tests, alguno tiene que fallar. Si no es así, falta cobertura o faltan casos.

Las mutaciones sobre las que decidí trabajar son las más sencillas: cambiar operadores aritméticos y de comparación, no mucho más que eso. Para hacer cambios más complejos hay dos opciones: usar un AST[8] y ver como manipularlo o generar más mutaciones que serán eliminadas por el compilador (o interprete en la primera etapa).

El proceso consiste en tomar un archivo fuente, obtener los tokens, generar un archivo fuente variando un token, luego otro y así, siempre ejecutando los tests y esperando que no falle, en cuyo caso, se corta y avisa al humano para que mejore los tests.

En [9] cuento superficialmente mi experiencia académica de uso con Jumble, un mutador de código para java y otras técnicas de automatización de test. Este proyecto es resultado de haber tomado el curso "Generación Automática de Tests Unitarios" (ECI 2012 N2) [10] a cargo de Nazareno Aguirre [11], Universidad Nacional de Río Cuarto, Argentina

Alcance

Mis metas fueron haciéndose cada vez más ambiciosas:
  1. mutador de codigo para php
  2. framework para varios lenguajes
  3. usando erlang
  4. con procesamiento paralelo y distribuido
  5. comprender el trasfondo teórico
  6. para poder usar otros lenguajes que no tienen el tokenizador tan accesible y hacer mutaciones más complejas
He decido que hay tres etapas y sólo transitar la primera, quizás la segunda. Eventualmente, si surge la necesidad, la tercera.

Loops


Un problemas interesante que intuí es que si tocás la condición de un loop, podés entrar en infinito. Esto produce que haya que darle un tiempo máximo de ejecución a cada test. Lo que debe hacer al comienzo el framework es una ejecución normal de referencia en cada nodo para tomar el tiempo y usarlo como base para el timeout de las ejecuciones sobre el código mutado. Si hay timeout, considero que el test falló, o sea que esta ok.

Código original:

for ($i = 0; $i < 5; $i++)

Mutaciones:

for ($i = 0; $i <= 5; $i++) -> debería fallar el test

for ($i = 0; $i <= 5; $i--) -> loop infinito

Aunque luego exploro la posibilidad de controlar de modo selectivo algunas mutaciones, lo que implementé fue ejecutar un test de referencia para tomar el tiempo y luego usar timeout para cortar en caso de anomalía.

 

Combinatoria


Otro problema es la combinatoria. Si para cada token que es mutable se hace toda la combinatoria con los otros, el número puede llegar a ser muy, muy alto. No sé por que esa fué mi primera idea, pero pronto caí en cuenta de mi error y sólo se hacen las combinatorias para cada token independiente de los otros.

Dado

if a + b == c

tenemos

if a (+,-,*,/,%,^) b (<,>,<=,>=,!=,==) c

Si combinara todo tendría  6 x 6 = 36 ejecuciones, pero sólo hago 6 + 6 = 12. Con dos tokens mutables parece poca diferencia, pero si fueran cien tokens tendriamos 6100 = 6100 en lugar de 6 x 100 = 600. Unos pocos segundos contra algunos millones de años.

Falsos positivos


El siguiente código tiene una mutación que es inocua ya que produce el mismo comportamiento y sin embargo parece un error:

<?php 
$i = 0;
while ($i != 5) {
   print "$i\n";
   $i++;
}
<?php 
$i = 0;
while ($i < 5) {
   print "$i\n";
   $i++;
}

Queda un criterio humano final para decidir.

Concurrencia


Otro problema más, relacionado a la ejecución simultánea de los tests es la concurrencia sobre las distintas versiones del código. Una manera es replicar el ambiente para cada instancia. Dije "instancia", no "nodo", o sea que en una misma máquina con N cores hay N instancias. La otra es... tuve un esbozo pero lo olvidé, ya volverá cuando haga falta.


Arquitectura



La comunicación entre el componente del lenguaje y el core es mediante un mensaje json[12]. El componente del lenguaje debe identificar cada token con su valor y su clase. Había pensado que los tokens no mutables se simplifiquen en uno solo, de modo tal que:

if a + b == c {
  printf("hola");
}

se tokeniza:

if
a
+
b
==
c
{
printf
(
"hola"
)
;
}

y se simplica:


"if a" 
+ operadorAritmético
b
+ operadorComparación
c { printf("hola"); }

pero esto es optimización prematura, y le da a cada componente de lenguaje la responsabilidad duplicada de simplificar, así que decidí que el core se encargue de simplificar, aun así es responsabilidad del componente del lenguaje identificar la clase:

if       -> inmutable
a        -> inmutable
+        -> operadorAritmético
b        -> inmutable
==       -> operadorComparación
c        -> inmutable
{        -> inmutable
printf   -> inmutable
(        -> inmutable
"hola"   -> inmutable
)        -> inmutable
;        -> inmutable
}        -> inmutable

y definirla:

operadorAritmético extiende simétrica
   mutaciones: +,-,/,%,*

habiendo dos tipos de clases, las simétricas y las asimétricas, esto es porque hay elementos completamente intercambiables y otros que no:

operadorControl extiende asimétrica
   elemento: break
     mutaciones: continue, exit, blanco
   elemento: exit
     mutaciones: blanco
   elemento: continue
     mutaciones: break, exit, blanco

Por ejemplo, clone se puede reemplazar por =, pero no al revés.

El mensaje en json se parece a esto:

{
"header": [
     {"version":"1.0"},
     {"language":"php"}
   ],
"classes": [
     { "name":"assignment",
       "type":"symmetric",
       "pool":[ "+=","-=","*=","/=",".=","="]
     },
     { "name":"clone",
       "type":"asymmetric",
       "genes": [ {"gene":"clone",
                  {"pool":["="]
                ]
     },
     ...
  ],
"tokens: [ {
     "class":"inmutable",
     "value": "<?php"
   },
   {

     "class":"inmutable",
     "value": "$a"
   },
   {

     "class":"assignment",
     "value": "="
   },
   .... 
 ]
}

Aunque firmemente empeñado en no hacer el componente de python y mantener el alcance del proyecto reducido, recordé que hay un framework de unit testing de python llamado doctests [13] que tiene el código embebido en el mismo archivo fuente, como comentarios. Era lo que se usaba en w3af[14], pero me ha dicho Andrés, el papaito de w3af, que no escala.

Si el mutador mutara el código de testeo estariamos en problemas, ¿no? Hay que inventar algún mecanismo para lidiar con esto. Me imaginé, no probé, que el tokenizador lo consideraría comentario y no habría inconvenientes. Pero un segundo antes, ya había hallado la solución, que por casualidad sirve para lidiar con los falsos positivos y los loops.

La idea es usar una nueva clase "signal", que indique al core si debe mutar o no. Por ejemplo

<?php

se convertiría en:

{
 "class":"signal",
 "value":"<?php",
 "action":"start"
}


o en dos tokens, el normal de "<?php" y el signal, al que le podríamos quitar "value". Me gusta más la segunda opción, así tenemos dos canales, el de datos y el de control.

El componente python debería insertar como primer elemento:

{
 "class":"signal",
 "action":"start"
}


Luego, en el código, habría que insertar comentarios con una etiqueta arbitraria, a gusto de quién implemente. En php será #code_mutator_start|stop.

while... /*#code_mutator_stop*/ ) {
  ...
#code_mutator_start
  ...
}


Esto es feo, pero simple.

Uno puede sentir la tentación de usar tambien "action":"pause", como para que el core tenga un comportamiento distinto según sea "pause" (sigue aplicando la lógica) o "stop" (concatena todo el resto), pero sería optimización prematura.

La precaución que hay que tener es como procesar en el componente el manejo de estas etiquetas, ya que el código del componente debe ser mutable.

Por ejemplo,

if ($value == "#code_mutator_stop" )

provocaría que el core deje de mutar no siendo esa nuestra intención.

Se soluciona asi:

if ($value == "#code_mutator"."_stop" )

Esta técnica para falsos positivos abre la puerta a solucionar los loops infinitos, usando una nueva action:
{
 "class":"signal",
 "action":"restrict",
 "genes":["-=","/="]
}


Esto le avisa al core que no puede usar esos genes en los próximos pools, hasta que venga una nueva restricción, que puede ser vacía. Por ahora, dejemos esta puerta cerrada.

Otra puerta que tampoco quiero abrir es una clase para poder mutar valores enteros.

Por fortuna, justo me puse a leer Domain Specific Languajes[15] de Fowler, lo cual me ayudó a reflexionar mucho acerca de este mensaje que circula desde los componentes hasta el core. No califica de DSL, pero sí aplican muchos conceptos.

En algún momento, había resuelto simplificar la estructura de los token:

"tokens: [ {
     "signal": "start"
   },
   {
     "inmutable": "<?php"
   },
   {
     "inmutable": "$a"
   },
   ....   ]


pero me encontré con un inconveniente. ¿Qué pasa si quiero pasar metadata, como por ejemplo la linea y columna del token en el archivo fuente? Esto es algo indispensable para python:


"tokens: [ {
     "signal": "start"
   },
   {     "inmutable": "<?php"
   },
   {
     "metadata": [{ "row":"1"},{"col":"0"}]
   },
   {
     "inmutable": "$a"
   },
   {
     "metadata": [{ "row":"1"},{"col":"6"}]
   },
   ....   ]
No me gusta, me complica el procesamiento, asi que mejor conservo la estructura original que permite procesar de modo mucho más sencillo elementos adicionales y le agrego un campo "info".

"tokens: [ {
      "class":"signal",
      "action":"start"
   }

   {
     "class":"inmutable",
     "value": "<?php",
     "info": [{ "row":"1"},{"col":"6"}]
   },
   {
     "class":"inmutable",
     "value": "$a",
     "info": [{ "row":"1"},{"col":"6"}] 
   },
   {
     "class":"assignment",
     "value": "=",
     "info": [{ "row":"1"},{"col":"6"}] 
   },
   .... 
 ]

Este ejemplo es una ilusión en php, que sólo provee información de lineas y lo hace mal. Python da más información.


Seguridad

Más por disciplina que por una necesidad real:

Los puntos de ataque son mensajes json mal formados y el resultado de la ejecución.

Resultado de la ejecución: el proceso se agota cuando se hacen todas las mutaciones o no falla alguna, asi que no hay denegación de servicio por ahí.

json: no hay loops, no hay denegación de servicio por ahi. Quizás si hay muchos tokens o tienen valores muy largos haya que tomar alguna precaución.

Lo que si podría ocurrir es que el componente inyecte código malicioso para que sea ejecutado luego por xUnit. Pero esto no afecta al core, no me importa.
Como Tom Lehrer le hizo decir a von Braun: "'Once the rockets are up, who cares where they come down? That's not my department', says Wernher von Braun."[16]

Ahora en serio, el componente podría por si mismo ejecutar el código malicioso. Sólo tendría valor en caso de que este sistema progresara a la arquitectura multinodo, para propagarse a otros nodos.



Alcance de la primera etapa

Me contento con que funcione una sola instancia que entienda php y python. Esto es para respetar el espíritu agile: sólo implemento lo que produce valor tangible. Yo no necesito ahora los componentes para otros lenguajes ni necesito analizar tanto código que justifique múltiples instancias. Si bien me vendría bien ver como funcionan otros tokenizadores, estaría invirtiendo trabajo en algo que quizás nunca desarrolle.


Esta visión aparentemente de corto alcance no se contradice con mirar al horizonte y hacer una apuesta, es por eso que el framework de ejecución tiene su core en erlang:

  • Es más sencillo pasar luego a una versión concurrente y distribuida
  • Me sirve para practicar. Aunque no al proyecto, me da valor a mi.
No es que me haya lanzado a codear con la mente en blanco, tuve casi dos semanas de análisis "colectivo" hasta que pude ponerme frente a una máquina

Como mencioné antes, los componentes han sido mutados, no así el core en erlang y bash, al que sólo apliqué eunit[17] y sshunit2[18].



El código completo está disponible en https://github.com/cpantel/codeMutator, hay un Makefile que corre diversos tests, pero antes hay que instalar unas dependencias, lee REAME.md

Me faltan varias refactorizaciones como poner la definición de las clases (las de los tokens) en modo composición para poder modificarlas sin tener que rehacer los tests. Tambien que los .sh sean más generales.


[1] http://es.wikipedia.org/wiki/Colectivos_de_Buenos_Aires
[2] http://stackoverflow.com/questions/246495/what-mutation-testing-frameworks-exist
[3] http://php.net/manual/en/book.tokenizer.php
[4] http://en.wikipedia.org/wiki/KISS_principle 
[5] http://docs.python.org/2/library/tokenize.html
[6] http://pic.dhe.ibm.com/infocenter/pdthelp/v1r1/index.jsp?topic=%2Fcom.ibm.debugtool.doc_11.1%2Fvrmu1mst109.htm
[7] http://www.ruby-doc.org/stdlib-1.9.3/libdoc/ripper/rdoc/Ripper.html
[8] http://en.wikipedia.org/wiki/Abstract_syntax_tree
[9] http://seguridad-agile.blogspot.com.ar/2012/09/automatizacion-de-test-eci-2012.html
[10] http://www.dc.uba.ar/events/eci/2012/cursos/aguirre
[11]  http://dc.exa.unrc.edu.ar/staff/naguirre/Pagina_personal_de_Nazareno_Aguirre/Principal.html

[12] http://www.json.org/
[13] http://docs.python.org/library/doctest.html
[14] http://w3af.org
[15] http://martinfowler.com/books/dsl.html
[16] http://en.wikipedia.org/wiki/Tom_Lehrer 
[17] http://www.erlang.org/doc/apps/eunit/chapter.html
[18] http://code.google.com/p/shunit2/

2013/07/25

Ethical carroñero

Objetivo: memoria


Años de entrenamiento no son en vano. ¿Recordás a Jason Bourne diciendo "I can tell you the license plate numbers of all six cars outside. I can tell you that our waitress is left-handed and the guy sitting up at the counter weighs two hundred fifteen pounds and knows how to handle himself. I know the best place to look for a gun is the cab or the gray truck outside"? Bueno, no es lo que me pasa a mi. Sin embargo, el otro día mientras caminaba pasé al lado de un gabinete maltrecho y me bastó una rápida ojeada para ver que no había sido depredado aún.

Regresé los quince metros que me había llevado evaluar que no podía llevar todo pues mi Ama y Señora me lo prohibe, pero sí la memoria. Una cuadra y media evaluar que no tenía donde probarla, que necesitaba todo y que tendría que desobedecer y volver. El instinto carroñero triunfó sobre la razón y subrepticiamente introduje en el santuario de mi hogar el gabinete más sucio por afuera que jamas nadie halla visto. Por dentro lo normal, hedía a cigarrillo y tenía telarañas, nada especial.

Objetivo: mother, micro, memoria y video


Si alguien tiró todo esto, razoné, no fue un servicio técnico, fué un civil. Estaba lista para funcionar, las únicas irregularidades eran que le faltaba una tapa, estaba deformado por un fuerte pisotón (alguien usó la computadora de escalera) y tenía una regrabadora de dvd desconectada. Como el disco rígido estaba, me imaginé que eso era lo que había fallado.

Me puse ambicioso, quizás funcione y tiene medio giga de ram, es usable. El único problema era que al conectarla, si la fuente estaba en corto me hallaría en problemas.

Arrancó lo más bien, hasta que en el POST[1] tiró los tres pitidos fatales. Así que la memoria o el mother estaban rotos. Cambié de slot e incluso la quité y no hizo diferencia. No me acordé de lo más elemental, limpiar los slots y los contactos,  cuando recordé, ya me había deshecho de los despojos.

La placa de video tenía los capacitores hinchados, se la regalé a un compañero de trabajo que suele resucitarlas.

Objetivo: disco y lectoras


La memoria me la quedo, pensé, algún día hallaré donde probarla. Se la regalé a otro compañero de trabajo. La fuente será backup de algo, al cajón del olvido. El micro, un P4 de 2.66 fue al estante de los micros, al lado del abuelo baldosa Pentium Pro 200. Vamos a por el disco de 80 GB y las lectoras.

Mi fiel Pentium III de 800 del 2002 se ofreció como voluntaria para probar los componentes. El disco arrancó pero se detuvo al inicio de windows. Luego pensé que podía ser de 64 bits y fallara por el cambio de hardware, linux avisa pero windows quizás no. Me dije, no importa, arranquemos de otro lado, un kali linux[2] debería sobrar.

En este momento obtuve el primer fruto intelectual: las computadoras no arrancan cuando se ponen dvds en las lectoras de cd.

Probé con un trinity [3] y todo ok, ahí estaba servida la vida de una persona.


Objetivo: información


La verdad es que no me agrada para nada escarbar en los asuntos personales ajenos, pero este era el escenario ideal: el disco de un persona absolutamente desconocida, con windows, que dista de ser mi fuerte. Era una excelente experiencia de investigación informática forense. Explorar esta máquina me iba a obligar a aprender a usar muchas herramientas y hacer algo que para divertirse es aceptable pero insoportable para trabajar: aprender windows internals.

Ya había cometido un error metodológico. El haber intentado arrancar con el sistema operativo del disco pudo haber destruido información, ya sea por que hubiera un bichito o porque windows restaurara algo para intentar salvarse.

El siguiente paso fue conectar el disco y ver que se podía extraer una imagen para no depender de que el disco fallara y no perder nada en las pruebas.


Tuve una interminable cantidad de dificultades técnicas, como que el adaptador pata/usb no funcionara y tuviera que usar la PIII. De todos modos, dijo la zorra, no tenía espacio libre en mi máquina. Que parted no entendiera ntfs shrink, etc.

Hora de priorizar y planificar.

Los objetivos estratégicos

  • Aprender el proceso completo.
  • Aprender las distintas herramientas necesarias.
  • La diversión.

Los objetivos tácticos y el alcance

  • No perder la imagen original.
  • Obtener credenciales.
  • Recuperar y continuar sesiones activas.
  • Armar un perfil del usuario.

Primer camino, la restauración


Este es el camino más sencillo. Intentar arrancar la imagen del disco dentro de una virtual y confiar en que no hayan bichitos, ni nada que me haga perder nada. El pronóstico es bueno, ya que la máquina fué descartada por fallo de hardware. Es vital que no se pueda conectar a Internet.



  • Extraje una imagen del disco.
  • Desconecté el disco original.
  • Dupliqué y copié la imagen a un disco externo como resguardo.
  • Reduje la partición de windows para que entrara en el disco interno.




Detalles

$ dd if=/dev/sdb of=windows.img

Para montar las particiones dentro de la imagen, que en este caso era una sola, hice esto[4]

$ fdisk -lu windows.img

Disk windows.img: 0 MB, 0 bytes
255 heads, 63 sectors/track, 0 cylinders, total 0 sectors
Units = sectors of 1 * 512 = 512 bytes
Disk identifier: 0x4fe54fe5

             Device Boot  Start        End    Blocks   Id  System
windows.img       1   *      63  160810649  80405293+   7  HPFS/NTFS



Hay que obtener el offset que es start * 512 y montar

mount -o loop,offset=32256 windows.img /mnt0

para obtener el espacio ocupado:

df /mnt -h


Reparticionar con gparted[5]

qemu-system-x86_64 -hda windows.img -cdrom gparted-live-xxx.iso  -boot order=d





Una vez reducida la partición, pude copiar parte de la imagen al disco interno

 fdisk -lu windows.img

Disk windows.img: 82.3 GB, 82348277760 bytes
255 heads, 63 sectors/track, 10011 cylinders, total 160836480 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x4fe54fe5

      Device Boot      Start         End      Blocks   Id  System
windows.img1   *          63    46000127    23000032+   7  HPFS/NTFS/exFAT


ahora hacemos End * 512 = 23552065024 Bytes
/ 1024 = 23000064 KBytes
/ 1024 = 22461 MBytes


dd if=externo/windows.img of=interno/windows.img bs=1M count=23000


El problema con este acercamiento es que perdí el espacio libre donde podía haber información, quedará para la autopsia. El archivo de swap ya lo había perdido antes, al probar si la máquina arrancaba.

Tras reiniciar un par de veces mientras el sistema operativo limpiaba el desastre, pude comenzar el análisis.

Segundo error: qemu provee networking por default, me di cuenta cuando el antivirus me dijo que se estaba actualizando.

-net none       use it alone to have zero network devices. If no -net option
                is provided, the default is '-net nic -net user'


Vamos otra vez, esta vez más generosos con la memoria, que por default es 256:

qemu-system-x86_64 -hda windows.img -net none -m 1G

Tras cometer una interminable serie de errores adicionales, llegué a la conclusión que tenía que comenzar otra vez. Para poder resetear la imagen hay que ejecutar otra vez el dd anterior.



Escarbando...


Tiene 2 antivirus y ccleaner, hay búsquedas en el historial de un mother y el nombre de la cuenta me cuentan que tiene alguna idea, no es un end-user.




Nada en la basura, 1G1 de música bajada de internet, links a soft ilegal.

La fecha de modificación de los archivos indica cerca de dos años sin uso.

Hay fotos personales con amigos, mascotas, contexto barrial.

Microsoft Office OneNote tiene muchas capturas de pantalla de... facebook

Quizás ya sabemos el nombre y la cara y varios amigos, el fondo de pantalla provee caras de amigos.

Ya sé donde estuviste de vacaciones: fotos de un complejo turístico con el nombre, no había información de gps en las fotos, pero si de marcas de cámara/celular, que se complementa con el soft de conectividad al teléfono.

Viendo el historial de navegación sé de que clubes sos:

Mercado pago
Mercado libre
hotmail
facebook
identidad racinguista...

Tengo algunas cookies, tipo 2800, no deben servir para nada

Ah, me olvidaba, tengo varias de tus contraseñas


Segundo camino, la autopsia

Consiste en analizar tanto desde linux como desde windows el disco sin escribir nada ni ejecutar nada que esté en disco, sin respetar el sistema de archivos. De hecho lo tomé para ver la información exif[6] de las fotos sin setear carpetas compartidas.

Debería corre un antivirus pero ya ha rendido suficientes frutos la experiencia y le dediqué demasiado tiempo.

Aprendizaje


Como se puede ver, una y otra vez, dejando de lado los errores, hay que hacer concesiones y no respetar a rajatabla el procedimiento forense. Si no tengo 80 GB libres en donde pueda hacer el análisis, tengo que sacrificar algo. Si hubiese tenido mucho apuro, sin tiempo para aprender a usar las herramientas, ni estar transfiriendo backups de un lado a otro, tendría que haber tomado atajos que de salir mal hubiesen provocado pérdidas de información. Además el cambio de objetivos durante el transcurso del proyecto, afectó a los pasos siguientes, cuando sólo me interesaba determinar lo que estaba roto, no tomé las precauciones necesarias para preservar la información.

Para hacerlo en serio, hay que tener en cuenta que se puede tardar mucho. En esa situación, que no haya espacio no es excusa para alterar la imagen original.

Conclusiones


Los elementos arrojados a la basura recuperados, según mercado libre tienen un valor de $460
  • Memoria pc400 512MB $110
  • Lectora cd $20
  • Disco 80GB $170
  • Fuente 430W $90
  • Micro P4 2.6 GH $70

Si alguien quiere comprar, ejem, me avisa...


Lo que sí pudo haber tenido valor es toda la información expuesta. En mi posición de ethical hacker, las copias del disco fueron sobreescritas y borradas. El disco sobreescrito, reformateado y reubicado.

Agradezco profundamente a Miguel Sumer Elías de Informática Legal por el asesoramiento gracias al cual intenté no incurrir en ningún delito.


Referencias


[1] http://en.wikipedia.org/wiki/Power-on_self-test
[2] http://www.kali.org
[3] http://trinityhome.org/

[4] http://www.jefferyfernandez.id.au/2007/06/14/mounting-partitions-that-are-within-a-disk-image/
[5] http://gparted.sourceforge.net/

[6] https://en.wikipedia.org/wiki/Exchangeable_image_file_format