FeelTheM7 Writeup oficial hackplayers

Tabla de contenido

No hace mucho me llegaba un interesante reto de un compi, Miguel Moreno aka M7, en la siguiente URL:

 

Como tantas veces y tantos «sujétame el cubata» me puse con ello y he de decir que me pareció muy completo y divertido. Así que antes de leer su writeup oficial traducido a la lengua de Cervantes por el muá tito Vis0r (en feliz equipo con Google Translator) te recomiendo sentarte e intentar resolverlo por ti mismo…

… si ya lo has resuelto o eres algo impaciente go ahead!:
 
Layer 1- Hacking web 
 
Empecemos. Lo primero que verás cuando abras el sitio web será una landing page para
descargar un archivo comprimido llamado caja de Pandora. Se te dará un consejo debajo de la descarga pero ten cuidado porque es la primera trampa del reto.
Después de insertar la llamada a la función en la consola del navegador, se te redirigirá a never gonna to give you up song, para resumir seras «rick rolled»

Layer 1.1 – Bypasseando las protecciones web

Para evitar que el reto nos redirija, primero debemos entender por qué sucede esto.
Inspeccionando el código fuente de la web, veremos que después de las importaciones de bootstrap, un JS (JavaScript)
script también se carga:

Entonces debemos inspeccionar este código, después de verificar, varias funciones necesarias para cargar la página,
seguro que
se nos ocurrirá echar un ojo a la función del tip givemeTheFirstPassword():

En esta función podemos ver que una sentencia if está comprobando que la variable booleana trollme es verdadera,
si es cierto redirigirá al rick roll si no devuelve el resultado de darle el const pass a
la función decodeBinary.
Aquí podemos tomar dos enfoques diferentes para decodear la contraseña.
Layer 1.1.1 – La manera fácil
La forma más fácil será copiar el contenido de la constante de pass (porque se define dentro del
contexto de la función, no será posible llamarlo directamente desde la consola) y pasarlo al
función de decodificación, esto devolverá la contraseña:

Layer 1.1.2 – La manera elegante

Este segundo layer es para la forma elegante de resolver esta parte, porque tendrás al menos conocimiento básico de JS y desarrollo web.
Si sabes cómo se ejecuta JS en el navegador, tu primera idea será cambiar el valor
de la variable trollme, bien, este es un buen punto de partida, pero pasará esto:

Se generará una alerta y se te redirigirá nuevamente al rick roll. Bueno, si revisas JS
script.js no hay evidencia de un interval (así es como se llaman las tareas programadas de JS). Sin embargo, si suponemos que hay algo comprobando si la variable trollme ha cambiado, debe ser
declarado en el HTML principal o en otro script importado del HTML principal.

Justo después de la importación del script.js podemos ver la declaración de otro script. Esto creará un interval para verificar si la variable trollme ha cambiado, podemos destruir este intervalo con el operador JS clearInterval(), eliminar esto y luego cambiar la variable recuperará el primer resultado del reto:

Layer 2 – Web fuzzing

Después de descifrar la primera capa con la primera contraseña, encontraremos la segunda capa del desafío (nota: en el nombre verá la capa 3, esto es un error de la parte del autor, pero decidió dejarlo, porque se dio cuenta mientras muchas personas lo intentaban y eso podía causar
malentendidos):

No hay pistas de dónde está la contraseña en el archivo comprimido y, como dijimos en la introducción, este reto es investigar, no hay ninguna guía, pero si revisaste todo el script.js, deberías haber notado algo extraño.
Justo antes de la función decodeBinary hay otra función llamada downloadWordList(). Si llamas a esta función, comenzará la descarga de una lista de palabras:

Lo primero que se te ocurrirá será bruteforcear la contraseña del archivo comprimido con hashcat o alguna otra herramienta. Pero primero debemos verificar cuál es el contenido de la lista de palabras. Si revisamos las palabras, veremos que el contenido del archivo es, de hecho, nombres de endpoints web, esto se puede ver, por ejemplo, en el nombre «.config», «.bash_history» nombres de archivos ocultos que generalmente se pueden encontrar en máquinas UNIX. 

En resumen, esto nos está dando la pista de que se debe realizar un fuzzing de directorios en la página web para encontrar algo. Aquí al igual que en la primera capa tiene dos enfoques diferentes. 

Layer 2.1: solución más rápida 

Esta primera solución requerirá un poco de conocimiento de GitHub. Si revisas el dominio de la página web, verás que esta página está alojada en GitHub (https://mimorep.github.io/FeelTheM7/). Si tienes experiencia en alojar páginas web en GitHub, sabrás que esto alojará una página web dentro de un repositorio. Si el usuario no paga el
premium de GitHub, el repositorio, será público para alojar la página. Para conocer la dirección del repositorio, debemos construir la URL teniendo en cuenta lo siguiente:

  • https://github.com/<username>/<repositoryName>
  • https://<username>.github.io/<repositoryName>/ 

Entonces teniendo esto en consideración la URL del repositorio, si el usuario no tiene una cuenta premium será:
 

  • https://github.com/mimorep/FeelTheM7 

Ahora que tenemos el repositorio, solo tenemos que ir a la carpeta de páginas web (tenga en cuenta que esta está presente en la lista de palabras) y veremos el código fuente de ambas páginas:

si inspeccionamos ambas páginas, veremos en el archivo surrender.html la contraseña de la segunda capa hardcodeada:

Pero en este caso, a pesar de ser una solución legítima, recomiendo más la solución normal, por ser más realista.  

Layer 2.2 – Solución normal 

Para esta solución, deberíamos realizar un «dir fuzzing» en la página web. En mi caso, usaré la herramienta dirbuster (puede usar su herramienta favorita en su lugar, esta si lo prefieres).

 
Como se puede ver en la anterior imagen, la ruta “FellTheM7/web-pages/top-secret” devuelve un código OK (200), si hemos descubierto una ubicación secreta en la página web (descartaremos rutas como
como el favicon).
Por ejemplo, si usamos gobuster en su lugar, los resultados serán mucho más rápidos, pero solo obtendremos el endpoint principal de la solución:

Si navegamos hasta el endpoint descubierto «web-pages/top-secret.html», se encontrará la siguiente página de destino:

Como sugiere el tip, cuando se crean endpoints sensibles, se debe tener en cuenta los diccionarios y evitar usar rutas conocidas, pero tened en cuenta que la seguridad por ocultación no es seguridad.
Teniendo esta página si inspeccionamos el código, encontraremos un comentario bastante inusual:

Como podemos ver, el .html está comentado, pero si eliminamos el comentario, la frase resultará: “Never surrender.html”, así que coloquemos esa página en la URL:

Como puedes ver, se recupera la contraseña y se completa este reto.

Layer 3 – grep file

Probablemente, esta es el nivel más fácil de todo el reto. Después de descifrar la capa zip con la contraseña del ejercicio anterior, los siguientes archivos se encontrarán en la tercera capa:

Aquí habrá nuevamente otro archivo comprimido protegido por contraseña y, en este caso, un archivo de texto con el nombre GrepMeBBy.txt con el contenido:

Como bien sugiere el nombre, la solución de este reto es simplemente ejecutar comandos grep (u otras formas de encontrar cadenas en grandes archivos txt sin formato), para encontrar la contraseña correcta. Por ejemplo, si encontramos por “contraseña” encontraremos el primer bait del ejercicio:

Sí, una solución será encontrar cada frase que creamos que podría estar antes del token real, pero en este ejercicio, tenemos que ser más inteligentes que eso. Con el primer match podemos ver un patrón: 

 <nombre_clave> : {<token>}  

Entonces, podemos hacer una búsqueda con expresiones regulares para obtener todas las cadenas coincidentes y obtener todo de una vez. En este caso, la expresión regular que usaremos es:
 

\:\{.*?\} 

Eso devolverá todo carácter o número que esté entre paréntesis y antes de ellos dos puntos (esto será necesario, para ser más precisos con la búsqueda). Con esta expresión regular, se recuperarán todos los baits y la contraseña correcta (tened en cuenta que en lugar de grep se está usando VS Code, pero los resultados deberían ser los mismos):

Como se puede ver en la imagen anterior, no se encuentran contraseñas con la expresión regular. Pero se encuentra un buen descubrimiento, si revisas el resultado de la clave token te diría lo siguiente:  
 
token:{DidYouReallyThinkItWouldBeThatEasy,ThinkLikeAHacker?}
 
Los profesionales de ciberseguridad y los hackers siempre le dicen a los usuarios que creen contraseñas seguras, una de las sugerencias más comunes es lo que se conoce como “las contraseñas de los hackers” o «contraseñas con cambios» que consiste en intercambiar algunos de los caracteres de la contraseña por unos especiales. 
 
Un ejemplo será cambiar «a» por «@» o cambiar «o» por «0». Aplicando esta técnica, la clave «token» dará como resultado «t0k3n» y si encontramos esto en el archivo, obtendremos la contraseña real.  
 
Tened en cuenta que el resultado está contenido entre «[]» en lugar de «{}» como el otro para hacer las cosas más complicadas:

Layer 4 – Stego Forensics

Después de descifrar la capa 3 con la contraseña, verás algo interesante. En este caso, no habrá más archivos comprimidos dentro, en su lugar, estará presente un archivo .wav familiar:

Lo primero que debemos hacer, como investigadores forenses, es verificar dos veces si el archivo es lo que dice (nunca ejecuteis archivos sin saber qué son). Para hacerlo, debemos buscar el número mágico del archivo (estos bytes son utilizados por el sistema operativo para identificar el binario que abrirá el archivo):

Como se puede ver en la imagen el archivo es lo que sugiere, un archivo WAV. Si reproducimos el archivo wav, se reproducirá un sonido familiar, sí, es un rickroll nuevamente. Pero si asumes que debes ser “rickrolled” y escuchar la canción, puedes notar que en algunas partes de la canción hay demasiado ruido.
 

Y otra cosa que puede venir a tu mente será, realmente 10 MB para una canción de solo 1 minuto en formato wav y con esta calidad de mierda, algo extraño está sucediendo aquí. 

Ahora abramos el archivo .wav con «Audacity» para inspeccionar el sonido.

Si prestas atención, se pueden ver dos grandes crestas en la forma de onda, estas dos coinciden con el ruido que se escucha al reproducirlo (te recomiendo que reproduzcas esto por secciones), eso te dará una pista de que algo se esconde allí. con estenografía.
Para ver lo que hay en las crestas, podemos usar la vista de espectrograma de Audacity, y se encontrarán algunos resultados interesantes:

Sé que ahora estarás pensando, para qué una contraseña, ya no quedan archivos comprimidos. Es posible que haya descubierto que el archivo es demasiado grande para solo un minuto de audio, pero si no es así, «extractTool.html» le dará la pista, sí, el siguiente archivo comprimido está dentro del archivo WAV.
Para descargar la herramienta de extracción (también puede usar una que ya tenga), deberás ir nuevamente a la página:
https://mimorep.github.io/FeelTheM7/endpoints/extractTool.html

Se iniciará una descarga automática y se colocará un programa de python en su máquina. El contenido de la herramienta no estará terminado y será tu trabajo terminar el script:

Una solución final del script puede ser como la siguiente (pero hay muchas opciones disponibles):

Con esto, podremos extraer el último archivo comprimido y descifrarlo con la
contraseña.
 

Layer 5: parcheo y reversing.
 

Una vez que finalmente hayas recuperado el archivo comprimido, el contenido será:

Como puedes ver, este parece ser el último reto y de hecho lo es. Este es un binario simple que tendremos que reversear o parchear para obtener la solución final y la forma de reclamar tu lugar en el salón de la fama.  

Si ejecutas el binario (no te preocupes, no es malware, pero si no me crees puedes pasar el hash a Virus Total o herramientas similares, se ha subido a VirusTotal, es posible que algunos proveedores detecten el binario como malware, pero como lo vas a reversearlo vas a ver
eso no es malware en absoluto). 

El programa está escrito en código C y compilado con Visual Studio Compiler, la decisión de crear el programa en código C es solo para hacer que el reversing sea un poco más complicado (C es conocido como uno de los códigos más difíciles de reversear, por ser uno de los lenguajes de programación de más bajo nivel).  

El código fuente se muestra aquí, no será accesible durante el reto porque está precompilado, pero creo que es un buen punto para proporcionar el código para una mejor comprensión de su comportamiento.

Layer 5.1 – Comprender el código fuente.  
 
En esta sección, se presentará desde cero el código fuente del programa que tuvo que ser reverseado, esto se hace para dar una mejor comprensión de cómo abordar un análisis adecuado y ver
el reto desde el lado del desarrollo de malware.  
 
Lo primero que debe hacer un desarrollador de malware es verificar si su software está siendo analizado por un reverser, esto se puede realizar con varios métodos: verificar los indicadores de depuración, crear un segundo proceso para verificar el principal, verificar entornos de reversing…
 
En este caso como digo antes, esta pieza es simple por lo que solo nos fijaremos en el número de núcleos de la máquina que está ejecutando el programa:

Generalmente, los reversers utilizan entornos virtuales para probar el malware, por lo general, si el reverser no tiene suficiente experiencia, no cambiará la configuración de la máquina para que parezca un
normal.

Considerando que las máquinas normales tienen cuatro o más núcleos de CPU, si la máquina tenía dos o más
menos núcleos, podríamos suponer que es una máquina virtual, y no nos interesará dejar
el código pasar por el flujo normal.  

En una situación real, no informaremos al reverser que
sabemos que es una máquina virtual, haremos las tareas normales para ocultar el payload real del malware.
 

Aquí también se realiza otra verificación, el «#» es un operador reservado en código C, que se usa para definir
instrucciones precompiladas. En este caso, estamos revisando la distro del host, si no es WIN32
(host de Windows) también evitará la ejecución.
Pero en este caso, no tiene sentido esta verificación, porque el binario está compilado puramente para Windows (tiene dependencias de WinAPI), esto se puede ver ejecutando un comando de archivo en el binario.

A veces, si solo queremos identificar rápidamente los IOC (indicadores de compromiso), lo más fácil es simplemente ejecutar strings en el binario. Strings es una herramienta nativa en la mayoría de las máquinas UNIX,
pero para hosts de Windows deberemos descargarlo de sysinternals.
Esta herramienta extrae todas las cadenas contenidas en el binario, y quizás podremos encontrar si no está bien protegido un beacon o alguna otra información útil.


Al ejecutar esto, recuperaremos información útil, pero el autor había hecho una técnica para evitar que esto sucediera.

Solo con esta información, el objetivo es más claro, se pueden extraer las siguientes conclusiones: 
  • Hay una URL en alguna parte del binario (podemos ver el https://).
     
  • El proceso de parcheo parece tener dos partes (hay una frase que nos dice «Algo
    falta para completar el desafío”).
     
  • Podemos ver la sentencia anti VM.
     
  • Parece que el resultado final estará en un archivo con el nombre FeelTheM7.txt creado en
    la misma ruta de ejecución (“./”). 
Pero ahora sé que estás preguntando «¿cómo diablos no está el token en una cadena?». Me gustaría decir
que la respuesta es simple pero no es tanto. Déjame presentarte de nuevo el
operador ”#” reservado en C.

Como se comentó antes, el operador “#” está reservado en C para definir operaciones de precompilación. En este caso, el programa está creando tres funciones, para ocultar el contenido de la cadena y luego en la precompilación del binario, esto se denomina técnicamente «macros».
 

Solo para resumir, la función HIDE_LETTER reemplazará la letra por otra basada en su código de operación, para luego ser reconstruida con UNHIDE_STRING. Un array con el resultado final contiene la cadena oculta durante el tiempo de ejecución, pero no mostraré el contenido de este array, al final el objetivo de este ejercicio es aprender, por lo que no se proporcionará el resultado final.

Pasemos ahora a la parte principal del programa. En esta parte encontraremos una declaración if que evita que ocurra la ejecución, esta será la tarea para parchear esta instrucción y permitir que el flujo del programa continúe:

Esto desbloqueará la parte final del reto. Ahora necesitaremmos revertir el código y comprender lo que está sucediendo aquí. El programa buscará un archivo específico llamado FeelTheM7.txt
en caso de que esté en la misma ruta que el binario, mostrará la solución y la escribirá en este archivo.

Ahora centrémonos en cómo podemos analizar el binario con otras técnicas.

Layer 5.2: reversear y parchear el binario.
 

En esta sección partiremos del análisis de strings realizado en la sección anterior y
tienen dos partes diferenciadas, la estática y la dinámica.
Tened en cuenta que la solución se puede obtener con ambas técnicas, y podemos elegir lo que queramos.

Generalmente el análisis dinámico es más fácil, pero también más inseguro (al final estás
ejecutando el binario), si me preguntas, un buen reverser siempre elegirá el estático, a menos que
el tiempo vaya en contra (pero la solución perfecta será usar ambos).
 

Layer 5.2.1 – Análisis estático
 

Para el análisis estático, usaremos en este caso Ghidra, ¿por qué? Porque es gratis y cualquiera puede tenerlo. IDA por ejemplo, a pesar de ser para mi mejor, tiene una versión de pago que no es accesible para
todos.
 

Después de crear el proyecto con el binario, simplemente arrastramos el binario a la carpeta y lo soltamos, Ghidra
detectará automáticamente la necesidad de su configuración:

Analiza el binario con la configuración predeterminada, espera hasta que finalice la ejecución y ahora estamos
listos para empezar.

Si recordamos el resultado de las strings, de la parte anterior, ya sabemos algunos
de las cadenas clave.  

Vayamos a «Felicidades, llegaste hasta aquí, ¿serías capaz de
¿Omitir esta declaración if? (presionamos la tecla «S» para abrir el indicador de búsqueda), porque parece
ser la primera cadena mostrada (podemos verificar esto dos veces ejecutando el binario en un entorno seguro).

Después de buscar la cadena, debemos hacer doble clic en FUN_00401030:00401048, porque allí está la referencia donde se está utilizando la cadena.

En este punto, lo suyo es abrir la vista de gráfico (más rollo IDA), pero si te sientes más cómodo con la otra vista, usa la que más te guste. Para el flujo del binario, parece que estamos en la función principal del programa (todavía tenemos que verificar esto dos veces, pero la estructura del programa parece estar bien).

Para comprender mejor el flujo de ejecución, podemos reorganizar los cuadros (siempre se recomienda cambiar el nombre de algunos cuadros si conocemos su comportamiento, por ejemplo, aquí cambia el nombre del cuadro de salida a FIN), por ejemplo, para que parezca que la instrucción Next fuera una instrucción If que está saliendo de la ejecución del programa:

Podemos ver claramente que el cuadro superior izquierdo es el punto de entrada de la función principal, donde el programa nos felicita por haber llegado tan lejos, luego se detecta una declaración if clara (JNZ). Este comparará el valor de DAT_0041618 y dependiendo del valor saltará al “Parece que tú…” que luego saltará a la salida del programa.
 

Si prestamos la debida atención veremos que el recuadro inferior izquierdo tiene una cadena con el nombre “Lo estás haciendo bien” esta parece claramente la ruta por la que queremos enviar el programa, de lo contrario el
terminará la ejecución.
 

El condicional del salto se realiza con un JNZ (Jump non Zero), por lo que para puentear y parchear esta parte tenemos dos opciones: 

  • Cambiar el JNZ por JMP, esto saltará siempre a la casilla que queramos.
  • Cambiar el JNZ a JZ (Jump Zero), ya que sabemos que el binario nunca salta a esa parte, la inversión de la comparación de los valores también realizará el salto. 

Una vez que hayamos localizado el salto a realizar, invertiremos la instrucción, en este caso, cambiando el JNZ a JZ (en el LAB_004107d), para parchear la instrucción if:

Ahora simplemente exportamos el programa compilado y verificamos cómo ha cambiado el flujo de la instrucción if:

 


La salida del programa, ahora nos dirá que falta algo más para completar el reto, por lo que debemos seguir bajando en el flujo del programa para descubrir de qué se trata:
 

 

Si seguimos buscando el flujo del programa, encontraremos la definición de un file handler, esto lo podemos ver si observamos la llamada a la API de Kernel32.dll que se está realizando (->KERNEL32.DLL::FindFirstFileA), si buscamos este método en el documento oficial de Microsoft podemos ver que este método busca un archivo o directorio con el nombre dado.  
 
Comprobando el flujo, podemos ver claramente los dos flujos diferentes del controlador, el primero sería el de error y redirigirá el flujo de programas a la salida del programa, el otro manejará la ejecución real.
 

 
Si nos fijamos en el cuadro inferior, una cadena «FeelTheM7.txt» y luego vemos la operación strcmp (comparación de cadenas). Con toda esta información y sabiendo cómo funciona el método FindFirstFileA podemos decir claramente que el programa está buscando un archivo con el nombre FeelTheM7.txt.
 
 
Sigamos mirando lo que hace el programa, el siguiente bloque que vemos contiene la siguiente cadena “Felicitaciones, completaste…” para que podamos confirmar nuevamente que este es el camino que estamos buscando.  
 
Después de ver esa cadena en el último bloque, se usa la instrucción «fopen» en el archivo
“FeelTheM7.txt”, por lo que el programa escribe algo en el archivo y luego cierra el identificador y finaliza la ejecución.
 

 
Tomemos el enfoque más fácil, sabemos que el programa busca un archivo con el nombre «FellTheM7.txt» y escribe algo (que está claramente ofuscado) en él, así que creemos el archivo y ejecutemos el binario parcheado:

 
Si, es así de simple, el reto está resuelto (evidentemente el resultado final está pixelado).
 
Os animo a que lo probéis por vosotros mismos
 
Layer 5.2.2 – Análisis dinámico 
 
Como os dije en la primera parte, siempre prefiero el análisis estático, para mí es más divertido, pero el análisis dinámico es muchas veces la forma más rápida, así que profundicemos en esta solución.  
 
En este caso, mantengo la premisa de usar herramientas libres, estaré usando un  depurador x64.  
 
Después de arrastrar y soltar el binario al programa, lo primero que haremos será buscar cadenas, las extraídas antes.
 

 

Al igual que en el análisis estático, buscaremos la cadena «Felicitaciones, llegaste tan lejos…» y verificaremos el código ensamblador de esa sección. Establezcamos un punto de interrupción aquí, para detenernos justo antes de que se imprima el mensaje:
 

 
Si ahora analizamos con detalle las instrucciones, lo que parece ser un if estará ubicado en el offset 00971055, donde se está comparando algo, y dependiendo del resultado de la comparación se da un salto. Si el resultado es cero, podemos ver que los flujos van a una sección donde se imprime la cadena “Parece que…”, pero si no es cero, irá a una sección donde se imprime la cadena “Lo estás haciendo bien, sigue”. 
 
Entonces, cambiemos la instrucción a la inversa:
 

 

Si lo hemos hecho correctamente, el flujo del programa cambiará y estaremos ahora en la siguiente sección, donde queremos estar:
 

 
Si continuamos con el programa, aparecerá la frase “Falta algo para completar el desafío”, por lo que debemos reiniciar la ejecución y encontrar lo que sigue para parchear. Para entender lo que está pasando, debemos «entrar» en algunas instrucciones de llamadas.
Después de haber verificado todo el código ensamblador, concluiremos que esta cadena siempre se imprimirá después de parchear la instrucción if.  
 
Si continuamos desplazándonos sobre la ejecución del programa, veremos la llamada del método WinAPI «FindFirstFileA» y una cadena con el nombre que es el programa encontrando:
 

 
Entonces, en este punto, sabemos que el programa está buscando un archivo con el nombre «FeelTheM7.txt», si seguimos depurando la ejecución, veremos que, de hecho, estamos dentro de un bucle donde el programa está verificando cada archivo del directorio, para ver si «FeelTheM7.txt» está presente. 
 
Hagamos lo mismo que en la parte estática, creemos el archivo FeelTheM7.txt”, para que el depurador vaya por la ruta correcta.  
Después de hacerlo, el flujo cambiará y aparecerá un mensaje indicándonos que el
el resultado está en el archivo se le pedirá:
 

 
Si continuamos con el flujo del programa, entraremos en un gran bucle, antes de que se cierre el handler del archivo; de hecho, este es el proceso de desofuscación de la solución final (recuerda que la solución final está ofuscada y no se puede encontrar en las cadenas ).
 

Una vez finalizado el bucle, la cadena de resultados se deofuscará y se imprimirá dentro del archivo “FeelTheM7.txt”, siendo esta la solución final del reto.

Conclusión
 

Feel the M7 es mi primer reto de ciberseguridad de M7, que se realiza solo con fines educativos y para preparar a las personas para escenarios del mundo real, donde esta tarea se convertirá en la base diaria de un ingeniero forense o reverser. 

Si tienes alguna duda/sugerencia sobre el reto, puedes
ponte en contacto con el autor a través de LinkedIn. 

¡Recuerda permanecer siempre del lado de la luz y disfrutar! 

Nos vemos en el salón de la fama. 

 ~ El M7

INFORMACION DEL PUBLICADOR
Kamal Majaiti
Kamal Majaiti
Administrador de sistemas e informático por vocación.
COMPARTELO EN REDES
Publica un comentario

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.