25 abril 2014

Analizando el contenido de un fichero mediante patrones visuales

Antes de empezar este artículo me gustaría decir que no suelo escribir entradas que giren casi en exclusiva alrededor de software que he escrito yo mismo. El marketing se me da fatal. Sin embargo esta herramienta de la que voy a hablar ya tiene su tiempo, siempre me la llevo conmigo y ya han sido varias personas las que me han animado a publicarla (entre ellas NighterMan y más recientemente mi jefe en Tarlogic, Andrés Tarascó). Y como además voy a hablar de un tema que parece que no se ha tratado mucho, me dije, qué demonios, ¿por qué no?

El asunto es que a lo largo de estos últimos años yendo y viniendo de CTFs, teniendo que abrir archivos una y otra vez para saber qué se oculta en ellos, e incluso fuera del mundillo de la seguridad, en un ámbito más de "andar por casa" donde he tenido que navegar entre los escombros de un core o simplemente la mera curiosidad de saber qué se oculta en un archivo cuyo contenido desconozco me ha surgido la siguiente pregunta: ¿hay alguna forma de ir más allá? ¿existe alguna forma de optimizar ese primer vistazo que le damos a un archivo tan pronto lo abrimos con un editor hexadecimal?

La respuesta a todas estas preguntas es sí, gracias a cierto aspecto de los datos que solemos ignorar: los propios bits. Estamos muy acostumbrados a tragarnos grandes volcados en hexadecimal y eventualmente algún volcado ASCII a la derecha, del cual sólo podemos confiar en poder entender lo que dicen. Todo lo que no es ASCII se ve como un soplido de caracteres al azar, formando un extraño galimatías que solemos ignorar pensando en que "ya lo entenderá el ordenador".

Pero la verdad es que hay más. Mucho más. Para empezar, en los números 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40 y 0x80 se esconde una similitud que a simple vista pasa totalmente desapercibida. Lo mismo entre los números 0x55 y 0xaa para los que os haya tocado alguna vez un sector de arranque. E incluso en el código máquina de una arquitectura RISC. El problema es que esto, en hexadecimal, o se ve mal, o directamente ni se ve.


Estos nuevos detalles se pueden apreciar mejor gracias a una herramienta que escribí allá por 2011 después de mi primera RootedCON: biteye, y más recientemente vix. Y cómo no, ambas GPL y para GNU/Linux.



Biteye es un programa con un aspecto bastante extraño. Es un editor hexadecimal que para empezar, ni puede editar. Lo escribí cuando estaba aprendiendo a usar SDL y el resultado es una interfaz bastante sobria. Exactamente así:


Biteye, mostrando un ELF.


Biteye sólo responde a cuatro teclas: Arriba, Abajo, Av Pág y Re Pag. Lo único que podemos hacer es avanzar y retroceder a lo largo y ancho del archivo.



Parece algo muy elemental, sin embargo la verdadera utilidad reside en tres elementos: para empezar, el panel de la izquierda (que llamaré "panel de filas"), el de la parte inferior (que llamaré "panel de columnas") y el color del volcado hexadecimal en el centro. Empezaremos por este último, que será el más familiar para todos.


El volcado hexadecimal
Este volcado es como cualquier otro volcado hexadecimal, offset a la izquierda, bytes en el centro y ASCII a la derecha, mostrando 1024 bytes a partir de cierta posición del archivo. Lo particular de este es el color de los bytes:


El color no está elegido al azar por algún incomprensible sentido mío de la estética. La idea es calcada de algo que llevan haciendo herramientas como Hexplorer para Windows, y es que los bytes con un valor absoluto más pequeño se vean más oscuros que los bytes más grandes. Fijémonos en el 0x01, el 0x02 o el 0x10: tienen un tono mucho más oscuro que 0xd5, 0xec y 0xbb. Teniendo en cuenta que en los ejecutables y muchos otros archivos binarios la mayor parte del archivo está formado por ceros, tener estos colores que contrastan tanto en un mar de negrura nos ayudan a encontrar datos de una forma mucho más visual. Sin embargo, hay más: supongamos una tabla de offsets, o la propia .plt, donde cada entrada tiene un salto direccionado por una entrada correspondiente en la .got, las cuales suelen estar ordenadas y miden todas lo mismo. De este modo, las direcciones de la .got crecen de forma bastante lineal. ¿Cómo se ve esto?


Se ven como degradados. Y en parte es normal: no estamos haciendo más que variar linealmente el valor de los bytes y por tanto, su color. Esto puede ser de gran ayuda a la hora de identificar estructuras y la semántica de sus campos. En estos casos podríamos formular hipótesis sobre índices, claves foráneas o desplazamientos relativos de cualquier tipo.


Pero vamos un paso más allá: estas líneas se ven verticales porque los índices están alineados al ancho del volcado (16 bytes). Sin embargo, no todas las estructuras gozan de este alineamiento. Cuando hay un array de estructuras de este tipo, veremos grandes secuencias de diagonales como estas:





Esto no es algo que no se pueda ver con un editor hexadecimal con un ojo entrenado. De hecho, muchos lectores experimentados van a decir que esto no es nada nuevo, ciertamente no lo es. Sin embargo, lo importante aquí es ahorrarnos tiempo. Y los colorines, francamente, ayudan.



Existen muchas estructuras que se pueden dilucidar en este volcado, es cuestión de ir probando con archivos de distinto tipo e intentar razonar por qué vemos lo que vemos.



El panel de filas

Es aquí donde empezamos a alejarnos un poco de los editores hexadecimales tradicionales. Esta vista nos permite contemplar los bits del archivo distribuidos horizontalmente y es, en mi experiencia, la más útil de todas ellas. Cada secuencia de ocho píxeles horizontales representa un byte, pintándose de naranja los bits que están a uno y en negro los que están a cero. Además, dentro de los unos, se marca en un color más claro los bits más significativos y más oscuro los menos significativos. Esto nos permite saber dónde acaba cada byte. Sí, lo sé, esto es equivalente a un mapa de bits monocromo de ancho fijo:




Cada fila tiene un ancho de un píxel y representa 32 bytes, teniendo por tanto 32 bytes * 8 bits = 256 píxeles de ancho. En rojo se marcan los 1024 bytes que están siendo mostrados en el volcado hexadecimal. Esto nos permite mantener la perspectiva de la parte del fichero que estamos visualizando. 



Debo decir que esto tampoco es una revolución: este tipo de vista la he encontrado en algunos editores hexadecimales especializados en editar ROMs de videojuegos de Gameboy, con algunas opciones más sofisticadas como tomar los bits de dos en dos (o grupos potencia de dos más grandes) y asignar un valor de una escala de grises, pudiendo mostrar mapas de bits de distinta profundidad de color. Citaría la herramienta donde he visto esto, pero es que francamente, no me acuerdo (agradecería que alguien me la pudiese indicar, llevo años detrás de ella)



Un caso práctico: la cabecera ELF

Del mismo modo que para el volcado hexadecimal, las estructuras alineadas con los 32 bytes de ancho de la vista se verán como grupos verticales de píxeles, y aquellos que no lo están se verán como franjas diagonales. Sin embargo, en esta vista existe un conjunto de patrones que se suelen repetir muchísimo cuando analizamos ficheros binarios. Especialmente los ficheros ejecutables, que por la diversidad de sus estructuras internas son muy representativos y les dedicaré los siguientes apartados. Por ejemplo, el /bin/ls de mi sistema (Linux, x86_64) que acabo de utilizar en el ejemplo anterior. Examinemos sus primeros bytes:



De un vistazo podemos apreciar dos regiones diferenciadas, aunque realmente hay tres. Existe una primera línea que empieza con una línea (no más grande de un byte) de varios bits a 1. Esto es parte del número mágico de la cabecera de un ELF: "\x7fELF". Ese 0x7f inicial es en binario 01111111, de ahí todos esos bits con el mismo color. Así comienzan los primeros bits de la estructura de 64 bytes Elf64_Ehdr de este ejecutable. Como son 64 bytes, la cabecera ocupa apenas 2 píxeles de ancho.

Lo siguiente región se aprecia un poco mejor por ser considerablemente más grande. Estas cuatro columnas (que una simple división nos dice que deben medir 32 / 4 = 8 bytes de ancho) pertenecen a las entradas de la tabla de cabeceras de programa, o tabla de segmentos, que para este ejecutable tiene 9 entradas. Cada entrada de esta tabla, en 64 bits, tiene 56 bytes. Sin embargo, estos 56 bytes ni siquiera son múltiplo entero de los 32 bytes de ancho que tiene la vista de filas. ¿Cómo puede ser que lo veamos como columnas verticales y no como las diagonales propias de los arrays desalineados? Pues porque resulta que en este caso se ha dado una casualidad que realmente se va a dar en muchos otros ficheros, y tiene que ver con cómo se agrupan los campos en las entradas de esta tabla (cada una del tipo Elf64_Phdr):

           typedef struct {
               uint32_t   p_type;
               uint32_t   p_flags;


               Elf64_Off  p_offset;

               Elf64_Addr p_vaddr;
               Elf64_Addr p_paddr;
               uint64_t   p_filesz;
               uint64_t   p_memsz;
               uint64_t   p_align;
           } Elf64_Phdr;

Prácticamente todos los campos de esta estructura tienen 64 bits y por lo tanto ocho bytes de ancho. Los únicos dos campos que se escapan a la norma son p_type y p_flags que son de 32 bits y además están agrupados, por lo que podemos verlos como un único campo de 64 bits. Esto no es muy distinto a un array de 56 / 8 = 9 elementos de enteros de 64 bits. Además, como el valor absoluto de todos estos campos están por debajo de 0x100000000, sus 32 bits más altos (salvo para los dos primeros campos) van a ser nulos. Gracias a esta característica vamos a poder diferenciar cada campo del siguiente.


Otro detalle que merece la pena destacar es que teniendo en cuenta que la mayoría de sus valores son pequeños, la probabilidad de encontrar un bit menos significativo a 1 es mayor que la de encontrar uno más significativo igualmente a 1. En una arquitectura little endian va a provocar que los bits se agrupen a la izquierda, ya que los bytes menos significativos van a aparecer antes que los más significativos. Por contra, en arquitecturas big endian los bits se agruparán a la derecha ya que los bits más significativos están en posiciones de memoria más bajas que los menos significativos. Comparemos, pues, una cabecera del /bin/ls de SPARC64 (big endian):




Con la de mi x86_64 (little endian):



Evidentemente, la vista sigue dependiendo mucho del alineamiento, pero alineamientos a parte, podemos ver indicios de cierto gradiente de densidad de bits a 1 hacia un sentido o hacia el otro en función de la arquitectura. Cuando lidiamos, por ejemplo, con firmwares que no tienen ningún tipo de cabecera y por tanto desconocemos la CPU para la que han sido compilados, este tipo de vistas nos pueden ayudar a obtener información sobre la arquitectura subyacente.


Las siguientes líneas que aparecen sin ninguna estructura aparente son las secciones .interp (que tiene tamaño variable y contiene la ruta del intérprete del binario), .note, .gnu.hash, .note.ABI-tag y .note.gnu.build-id, que suelen tener tamaños dispares y normalmente suelen contener información del compilador usado y la ABI.


La tabla de símbolos dinámicos: .dynsym
La siguiente región que podemos encontrarnos tiene un patrón de ocho columnas, pero siendo todas ellas mucho más homogéneas que las ya vistas:



Este patrón pertenece a .dynsym, la sección del ejecutable que contiene la tabla de símbolos dinámicos. Cada entrada es del tipo Elf64_Sym:

           typedef struct {
               uint32_t      st_name;
               unsigned char st_info;
               unsigned char st_other;
               uint16_t      st_shndx;
               Elf64_Addr    st_value;
               uint64_t      st_size;
           } Elf64_Sym;

Tenemos de nuevo una situación similar a la de la tabla de cabeceras de programa: los campos se pueden agrupar de ocho en ocho, de ahí la regularidad. La columna "ancha" se debe al campo st_name: es un offset dentro de cierta sención (dynstr) con la cadena del nombre del símbolo descrito. Como la mayoría de campos son de tipo función y son globales (STT_FUNC = 2 y STB_GLOBAL = 1) se codifican en el campo st_info como (1 << 4) | 2, lo cual es 00010010 en binario y encaja con la columna de dos píxeles naranjas con dos píxeles de separación. Como el resto de campos están a cero, aparecen estas líneas negras entre cada dos líneas: todos estos símbolos hacen referencia a funciones importadas por el ejecutable desde otras bibliotecas.

Si nos fijamos, el patrón vuelve a cambiar hacia el final de esta sección. Esto es debido a que al final de todas las importaciones se describen símbolos que realmente han sido definidos dentro del ejecutable (como _edata, _end, __bss_start, etc), lo cual cambia algunos flags y hace que el contenido esté un poco más cargado de bits a 1.

Texto ASCII: cuando sólo teníamos 7 bits
La siguiente región es igualmente representativa de muchísimos otros ficheros binarios, y además tiene una característica importante compartida por absolutamente todos sus bytes:


Esto se corresponde con la sección .dynstr, y está formada exclusivamente por texto plano y algún que otro carácter nulo. Básicamente es una secuencia de todos los nombres de los símbolos definidos en .dynsym. Lo importante aquí es que todos los caracteres imprimibles de la tabla ASCII están siempre debajo de 128 (0x80 en hexadecimal), o sea que su bit más alto va a ser siempre cero. Esto provoca que se sucedan unas líneas verticales de ceros separando cada byte. El resto de los mismos parece bastante ruidoso (para las máquinas nuestra forma de escribir es un auténtico galimatías casi incomprensible), pero en fin, ¡qué podemos esperar del lenguaje natural! Suficiente tenemos con ese bit siempre a cero :)

Encontrar este patrón en un fichero con un formato desconocido suele ser buenas noticias, básicamente porque significa que es algo que nosotros podemos leer. No es nada que no pueda mostrar un strings, pero esta vista nos permite ver las cadenas en su contexto. Y si queremos leerlas siempre tenemos el editor hexadecimal a la derecha.

Torres binarias
A veces nos encontramos en un fichero binario largas sucesiones de números enteros que crecen en unidades que son múltiplos de potencia de dos y que además están alineadas. Cuando esto sucede, se forma un patrón en forma de media torre muy llamativo. Es llamativo porque es autosemejante a diversos niveles de escala: si nos fijamos, la mitad de esta torre es igual a toda la torre salvo una línea vertical.


Podemos hacer el experimento en una hoja de papel cuadriculado: si escribimos los números del 1 al 16 en binario uno justo debajo del otro, pintando los cuadrados en negro cuando se correspondan con un cero y dejándolos en blanco cuando es cero, veremos exactamente este mismo patrón.

Cuando vemos estas torres, debemos pensar automáticamente en el patrón en degradados que vimos en los colores del volcado hexadecimal, pues están directamente relacionados. Lo que en el volcado se ve como incrementos de brillo, aquí se ve esta forma particular. En la imagen, lo que estamos viendo son las entradas de la sección .plt de un /bin/ls de i386, las cuales tienen esta forma:

080494e0 <__ctype_toupper_loc@plt>:
 80494e0:       ff 25 00 20 06 08       jmp    *0x8062000
 80494e6:       68 00 00 00 00          push   $0x0
 80494eb:       e9 e0 ff ff ff          jmp    80494d0 <_init+0x30>

080494f0 <getpwnam@plt>:
 80494f0:       ff 25 04 20 06 08       jmp    *0x8062004
 80494f6:       68 08 00 00 00          push   $0x8
 80494fb:       e9 d0 ff ff ff          jmp    80494d0 <_init+0x30>

08049500 <raise@plt>:
 8049500:       ff 25 08 20 06 08       jmp    *0x8062008
 8049506:       68 10 00 00 00          push   $0x10
 804950b:       e9 c0 ff ff ff          jmp    80494d0 <_init+0x30>


Cada entrada en la .plt tiene exactamente 16 bytes, lo cual provocará que cada par de entradas esté alineado con nuestra vista de filas. Lo importante de cada entrada en la .plt es que hay tres instrucciones cuyos argumentos varían con una diferencia constante que es potencia de dos respecto del anterior: el primer salto lee las direcciones 0x8062000 el primero, 0x8062004 el segundo, 0x8062008. Lo mismo sucede con el push (0x4, 0x8, 0x10...) y con el siguiente jmp (que además crece de 16 en 16, también potencia de 2).

Debido a que hay dos filas por entrada, tenemos que ver 6 patrones en forma de torre binaria, que es exactamente lo que sucede.

Los poemas de las CPUs: del CISC romance al RISC oriental.
En general, no hace falta saber chino para diferenciar un texto chino de un texto en árabe, ruso, sánscrito o inglés. Básicamente porque los alfabetos son distintos y la forma de agrupar las palabras es igualmente diferente. Cuando pasamos código máquina de alguna arquitectura bajo los ojos de biteye, sucede exactamente lo mismo.

Hoy por hoy, las arquitecturas de CPU se dividen en dos grandes familias: las arquitecturas CISC y las arquitecturas RISC. En las arquitecturas RISC las instrucciones suelen tener el mismo tamaño en bytes y existen un número bastante limitado de ellas, aunque son suficientes para poder compilar un Linux y ejecutarlo correctamente. Ejemplos de arquitecturas RISC son ARM, PowerPC, SuperH o MIPS (esta última ni siquiera tiene una instrucción para meter un valor en un registro, en su lugar se hace sumando un cero con el valor que queremos inicializar dicho registro). En general, hablamos de un lenguaje máquina muy depurado donde incluso con un poco de práctica, se podría leer sin la necesidad de un desensamblador.

En el otro extremo tenemos a las CISC, entre las cuales las más conocidas son x86 y x86-64. Estas se caracterizan por tener un repertorio de instrucciones enorme, donde los operandos se pueden componer de muy diversas formas (haciendo además que las instrucciones puedan tener tamaños diversos) y con todo tipo de modificadores e instrucciones especializadas. Además, a diferencia de las arquitecturas RISC, donde el procesador nunca puede leer una instrucción "por la mitad" ya que deben estar obligatoriamente alineadas, en CISC esto no es así, y si utilizamos un desensamblador en una dirección incorrecta lo más probable es que obtengamos un falso desensamblado que incluso pueda llegar a ser ejecutando correctamente por la CPU (aunque evidentemente no hará lo que nosotros queramos).

Continuando con el símil del idioma, podríamos comparar las arquitecturas RISC con el coreano y las arquitecturas CISC con el francés: en el coreano todas las sílabas ocupan el mismo recuadro y están formadas por unos componentes muy simples. En el francés, sin embargo, las sílabas pueden tener todo tipo de longitudes que dependen de las letras involucradas, excepciones, acentos y el sonido que se quiera producir. Además, si empezamos a leer una sílaba "por la mitad" no podríamos saber a priori si la sílaba está cortada y obtendríamos una pronunciación que en absoluto tiene que ver con la de la sílaba completa.

Analicemos entonces cómo se ven las instrucciones de las arquitecturas RISC más famosas. Por ejemplo, ARM (little endian):


MIPS 32 (little endian):


MIPS 32 (big endian):


SPARC 32:


SPARC 64 (las instrucciones miden lo mismo que en SPARC de 32 bits y apenas se pueden notar las diferencias):



SuperH 4 (nótese que esta arquitectura tiene instrucciones de 16 bits, por tanto 2 bytes por instrucción y 32 / 2 = 16 instrucciones por fila):



Y PowerPC:



Debido al alineamiento obligatorio de las instrucciones RISC que convierten al código en algo equivalente a un array de enteros, siempre vamos a ver patrones verticales. 

Por otro lado, el código máquina generado para arquitecturas CISC suele carecer de alineamientos de ningún tipo, y a veces es difícil de distinguir de un chorro de bytes totalmente aleatorios.

Por ejemplo, un código de x86 en 16 bits sacado de FreeDOS:



Y de nuevo para x86, en 32 bits (lo que en el mundo de Linux se conoce popularmente como i386):


A medida que esta arquitectura fue evolucionado y el tamaño de sus registros se fue duplicando, el número de bits desperdiciados a la hora de representar números pequeños fue creciendo también (0x0004 -> 0x00000004 -> 0x0000000000000004). Aunque en general el código máquina de x86-64 maneja esto bastante bien, podemos ver trazas del desperdicio en la cantidad de bits a cero comparado con i386:


Sin embargo, estas tres no son las únicas arquitectura CISC que hubo en la historia. Por ejemplo, la también popular Z80 se ve así (esto lo he sacado tal cual de una ROM del Pokémon para la Gameboy Color y empieza a ser complicado ver algún tipo de patrón salvo por alguna que otra anomalía estadística en la cantidad de ceros):



Y para Motorola 68000 (donde una vez más, los ceros delatan la existencia de un código):



Otros estructuras
No todos los ficheros ejecutables que nos toque analizar son tan simples, y algunos de ellos ni siquiera están dentro de un formato conocido. Por ejemplo, en el mundo de los firmwares no es raro encontrarse datos comprimidos, donde la entropía es máxima (y que  por tanto no se pueden diferenciar a simple vista de datos cifrados cuando el algoritmo es robusto) y los unos y ceros se reparten de forma aparentemente aleatoria siguiendo una distribución uniforme:


También nos podemos encontrar algún que otro mapa de bits (esto es un Tux que me encontré en un kernel de Motorola 68000, supongo que es la imagen que aparece en la esquina superior izquierda del framebuffer cuando arranca el PC):



Existen además muchos sistemas (como por ejemplo, DOS) que almacenan fuentes tipo raster como mapas de bits monocromos, los cuales además suelen tener ocho píxeles de ancho. En casos como este, la vista de columnas se vuelve muy útil, ya que nos permiten visualizar la fuente tal cual se vería en pantalla, salvo quizá por el sentido horizontal en el que se almacenan. Por ejemplo, un fichero EGA.CPI de mi FreeDOS revelaba un contenido como este:


O volviendo a la dichosa ROM del Pokémon:


Y muchas otras cosas más que por brevedad dejaré al lector curioso que juegue con la herramienta para buscar "cosas raras" en el fichero que más rabia le dé, y saque sus propias conclusiones :P

Conclusiones
Hay mucho que ver más allá del hexadecimal.

La verdad es que si me salgo de los ejecutables podría extenderme con muchísimos patrones más, pero no creo que pudiese cubrir todas las áreas en las que alguien se podría especializar. Incluso se podría utilizar para evaluar la calidad de un generador de números pseudoaleatorios. Para poder usar correctamente esta herramienta es importante tener la mentalidad de intentar comprender el porqué de lo que se ve (comparar la vista de bits con el volcado es necesario, a pesar de todo), y gran parte de los descubrimientos que se hacen se deben a la experiencia con archivos conocidos y a unos ojos bien entrenados. 

Y por lo demás, sólo recordar que a veces es divertido visualizar un archivo sin la pretensión de encontrar nada.

Post Scriptum sobre la herramienta
Como mencioné antes, Biteye ha sido escrito originalmente como un pasatiempo, tenía curiosidad por cómo se veían los bits de un fichero y además de experimentar con SDL, y como tal el código apesta. Nunca esperé que llegase a ser una herramienta que fuese a liberar. En cualquier caso, el código original se pueden bajar de http://actinid.org/outcoming/biteye-0.1.tar.gz (Git web: http://actinid.org/git/?p=biteye.git;a=summary)

Cuando empecé a escuchar algunas opiniones favorables comencé a escribir Vix, una versión mucho mejor diseñada y también basada en SDL (Git web: http://actinid.org/git/?p=vix.git;a=summary, descarga lista para ser compilada: http://actinid.org/outcoming/vix-0.1.tar.gz) y que soporta una interfaz con ventanas que se pueden organizar para aprovechar mejor el espacio de trabajo, múltiples archivos abiertos a la vez, etcétera. El aspecto de Vix no se aleja mucho del de biteye:


El problema de Vix es que todavía está en pañales y es ligeramente más lento. Mi intención era añadirle algunas cosillas, como algunas combinaciones de teclas para saltar a un offset, desensamblar regiones (usando, por ejemplo, libbfd), cambiar el ancho de las vistas de bits, la profundidad de color, ¿integrarlo con radare? y quién sabe si soporte de edición. Como se puede apreciar, son muchas cosas en el TODO para lo que originalmente ha sido un juguete.

Desgraciadamente, no tengo tanto tiempo como me gustaría para implementar todas estas funcionalidades. Pero quién sabe, si al final esta herramienta acaba siendo de utilidad podría atreverme a priorizarlo un poquito :D

Artículo cortesía de Gonzalo J. Carracedo

4 comments :

ace dijo...

Pedazo articulo !!! y haces bien teniendo en cuenta lo que te dice tu jefe que yo cada vez que le llevo la contraria termino trabajando el doble o metiendo la pata jajaj

Saludos

newlog dijo...

muy currado!

Ahora, si os queréis hacer pajas de las buenas...
https://www.youtube.com/watch?feature=player_embedded&v=4bM3Gut1hIk

JJ dijo...

Este articulo es enorme, demuestra un gran conocimiento y experiencia. En cuanto tenga mi ordena a mano la pruebo. Muchas gracias!

BatchDrake dijo...

Exactamente lo que buscaba. Muchísimas gracias :D