Warning: This challenge is still active and therefore should not be resolved using this information. Aviso: Este reto sigue en activo y por lo tanto no se debería resolver utilizando esta información.
Intro
This crackme is for the crack challenge 6 of canyouhack.it.
In this crackme the goal is to turn on all the lights. Note that a light off to the next, so if we interrupt this, we win.
Tools
Exeinfo (For crackme info)
Delphi Decompiler (For decompile)
OllyDbg (For debug)
Decompiling
With Delphi Decompiler we can found easy the buttons and his offsets.
Go to the offset 401A64 in OllyDbg and analyze the code.
We view two jumps, one turn ON the light and the other Turn OFF the next light. Patching the call from offset 401A8B we get the serial.
Se nos proporciona la imagen anterior y se nos invita a resolver la ecuación para el menor entero positivo que representa cada fruta. La solución del reto es concatenar «Flag-» a la suma de los valores de las tres frutas.
Análisis inicial
El enunciado es breve pero ya nos adelanta que existen varias soluciones y que además sólo quieren los números más pequeños que podamos encontrar.
Las frutas están muy bien y son muy sanas pero a nosotros se nos hace más sencillo plantear la ecuación con letras.
Para arrojar algo de luz veamos la representación gráfica en 2D y 3D con la ayuda de Desmos.
Las gráficas más que luz nos traen oscuridad de modo que preguntemos a ChatGPT.
ChatGPT
resuelve: (x/y+z)+(y/x+z)+(z/x+y)=4 para enteros positivos
Dado que las variables x, y, z son enteros positivos, podemos intentar encontrar una solución para la ecuación bajo estas condiciones. Sin embargo, encontrar una solución exacta puede ser complejo debido a la naturaleza no lineal de la ecuación. Aun así, podemos intentar algunos enfoques:
Prueba y error: Podemos probar diferentes combinaciones de valores enteros positivos para x, y, z y ver si satisfacen la ecuación.
Análisis de casos específicos: Podemos comenzar analizando casos más simples, como cuando dos de las variables son iguales, y luego ampliar nuestra búsqueda desde allí.
Análisis gráfico o numérico: Utilizando herramientas gráficas o numéricas, podríamos identificar posibles soluciones y luego verificar si son enteros positivos.
Voy a realizar un análisis numérico con un rango limitado de valores para ver si encontramos alguna solución que cumpla con las condiciones. Vamos a probar con valores de 1 a 10 para cada variable.
El análisis numérico para valores de en el rango de 1 a 10 no ha encontrado ninguna solución que satisfaga la ecuación. Esto sugiere que, dentro de este rango, no existen combinaciones de enteros positivos que cumplan con la ecuación.
Dado que la ecuación es bastante compleja y no lineal, es posible que las soluciones, si existen, se encuentren fuera del rango considerado o que requieran un enfoque diferente para ser identificadas. Si tienes un rango específico de valores en mente o si hay alguna otra restricción o contexto relevante, por favor házmelo saber para poder ajustar el análisis en consecuencia.
Básicamente nos está invitando a utilizar prueba y error para encontrar una solución, y el prueba y error de las matemáticas no es más que la fuerza bruta en programación.
El código
Partiendo del problema anterior dame un código en python que realice una comprobación recursiva de todas las variables con un rango de 1 a 999.
soluciones = []
rango_valores_x = range(1, 999)
rango_valores_y = range(1, 999)
rango_valores_z = range(1, 999)
for x in rango_valores_x:
for y in rango_valores_y:
for z in rango_valores_z:
# Calculamos el valor de la expresión para cada combinación de x, y, z
valor = x / (y + z) + y / (x + z) + z / (x + y)
# Comprobamos si el valor es igual a 4 y agregamos la solución a la lista
if abs(valor - 4) < 1e-6: # Usamos una pequeña tolerancia para evitar errores de precisión
soluciones.append((x, y, z))
print(soluciones)
Dejo resaltada la línea 12 del código ya que en ella reside el problema. Está comprobando con un margen de tolerancia de 10-6 ya que el resultado de las diferentes fracciones no da como resultado exacto 4. Esto es un problema ya que nosotros necesitamos que de exactamente 4 para validar los valores enteros de x, y y z. Pongo un ejemplo para el que se haya perdido con una solución válida para la tolerancia 10-6 siendo x=2, y=264 y z=993.
En otras palabras, ChatGPT nos ha brindado una solución aproximada que no sirve para nuestro propósito. Seguimos probando con el código anterior quitando la tolerancia y con rangos mayores hasta que en 106 paro. Me acaba de quedar claro que con la fuerza bruta no vamos a ninguna parte, o más bien, no tenemos capacidad de computación para resolverlo de ésta manera.
¿Qué está pasando?
Lo que pasa es que estamos ante una ecuación algebraica de 3 incógnitas que deben ser enteros positivos cuya solución se alcanza mediante la teoría de curvas elípticas.
Curvas elípticas
Las curvas elípticas son fundamentales en matemáticas avanzadas, representadas por la ecuación y2=x3+Ax+B, donde A y B son constantes. Estas curvas son un punto de encuentro entre la geometría, la teoría de números y el álgebra, ofreciendo un campo rico para la exploración y el análisis. En este CTF, nos enfocaremos en los puntos racionales de las curvas elípticas. Utilizando el método tangente-secante, un procedimiento geométrico iterativo, buscaremos ampliar un conjunto finito de soluciones conocidas a la ecuación de la curva. Este método nos permite indagar en la estructura de las soluciones racionales, que potencialmente pueden ser infinitas. Además, estableceremos una conexión entre las soluciones enteras de las ecuaciones diofánticas y los puntos racionales en las curvas elípticas partiendo de la ecuación (1) especificada en el análisis inicial. A pesar de su aparente simplicidad, esta ecuación es conocida por presentar soluciones mínimas de gran tamaño.
Adecuación
Antes de nada, necesitamos saber el grado de la ecuación, de modo que planteamos la ecuación en forma polinómica estándar deshaciéndonos de los denominadores.
Ahora necesitamos expandir y simplificar para llegar a la conclusión de que estamos ante una ecuación diofántica de grado 3. Este proceso es engorroso por la cantidad de términos a manejar así que vamos a utilizar Mathematica como software de respaldo para finalmente obtener el polinomio en la forma de Weierstrass según la ecuación 4.
\begin{align}
& y^2=x^3+109x^2+224x\\
\end{align}
donde:
\begin{align}
x = \frac{−28(a+b+2c)}{(6a+6b−c)}\\
y = \frac{364(a−b)}{(6a+6b−c)}
\end{align}
Las relación entre la ecuación 3 y los puntos de la curva elíptica se establecen mediante la ecuación 4. Las transformaciones entre las soluciones (a, b, c) y los puntos (x, y) en la curva elíptica vienen dados por las ecuaciones 5 y 6. Con estas transformaciones, cada solución de la ecuación diofántica se puede representar como un punto en la curva elíptica, y las operaciones de suma de puntos en la curva elíptica pueden usarse para encontrar nuevas soluciones de la ecuación diofántica.
Mathematica
El código que tenéis a continuación pertenece al gran trabajo de Aditi Kulkarni [7], que además nos da el resultado para cualquier valor de n. Ojo porque para n=4 el resultado tiene 81 dígitos, para n=6 tiene 134, para n=10 tiene 190 y para n=12 asciende a 2707 dígitos.
(* Asignar un valor numérico a n *)
n = 4;
(* Definir la ecuación de una curva elíptica en términos de n *)
curve4 = y^2 == x^3 + (4*n^2 + 12*n - 3)*x^2 + 32*(n + 3)*x;
(* Encontrar un punto racional en la curva que no sea (4,0) *)
P4 = {x, y} /. First[FindInstance[curve4 && x != 4 && y != 0, {x, y}, Integers]];
(* Función para calcular la pendiente entre dos puntos en la curva,
o la derivada en el punto si son iguales *)
Slope4[{x1_, y1_}, {x2_, y2_}] :=
If[x1 == x2 && y1 == y2,
ImplicitD[curve4, y, x] /. {x -> x1, y -> y1},
(y2 - y1)/(x2 - x1)];
(* Función para calcular la intersección en y de la línea entre dos puntos
o la tangente en el punto si son iguales *)
Intercept4[{x1_, y1_}, {x2_, y2_}] := y1 - Slope4[{x1, y1}, {x2, y2}]*x1;
(* Función para encontrar el siguiente punto racional en la curva *)
nextRational4[{x1_, y1_}, {x2_, y2_}] :=
{Slope4[{x1, y1}, {x2, y2}]^2 - CoefficientList[curve4[[2]], x][[3]] - x1 - x2,
-Slope4[{x1, y1}, {x2, y2}]^3 + Slope4[{x1, y1}, {x2, y2}]*(CoefficientList[curve4[[2]], x][[3]] + x1 + x2) - Intercept4[{x1, y1}, {x2, y2}]};
(* Función para convertir un punto en la curva elíptica a una solución diofántica *)
ellipticToDiophantine[n_, {x_, y_}] :=
{(8*(n + 3) - x + y)/(2*(4 - x)*(n + 3)),
(8*(n + 3) - x - y)/(2*(4 - x)*(n + 3)),
(-4*(n + 3) - (n + 2)*x)/((4 - x)*(n + 3))};
(* Usar nextRational4 para iterar desde P4 hasta encontrar una solución
válida y positiva para la ecuación diofántica *)
sol4 = ellipticToDiophantine[n,
NestWhile[nextRational4[#, P4] &, P4,
! AllTrue[ellipticToDiophantine[n, #], Function[item, item > 0]] &]];
(* Escalar la solución para obtener enteros mínimos *)
MinSol4 = sol4*(LCM @@ Denominator[sol4])
(* Suma de las tres variables*)
Total[MinSol4]
Solución
Concatenando Flag- con el resultado de Mathematica tenemos la ansiada flag.
ChatGPT ha demostrado ser eficaz en el análisis y la resolución de problemas, siempre que se le proporcione el contexto adecuado. Sin embargo, es importante ser conscientes de que la respuesta proporcionada puede ser aproximada, especialmente si la solución requiere una gran cantidad de recursos computacionales. Por ejemplo, al trabajar con una ecuación diofántica y valores específicos para (x) e (y), ChatGPT puede ayudar a calcular puntos como (P), (2P), (3P), etc., pero hay que tener en cuenta que los resultados para estos puntos pueden ser estimaciones.
Finalmente, os invito a leer la solución de Mingliang Z. [4], en la que se resuelve el problema por completo y de forma muy detallada.
Os comparto un reto stego que me gustó cuando lo hice hace unos años. En realidad se tarda pocos minutos en resolverlo pero depende de tus recursos es posible que se te atragante.
Procesando a la víctima
Cuando te has enfrentado a unos cuantos retos stego lo normal es que tengas un arsenal de herramientas por las que vas a pasar a la víctima. En mi caso cuando se trata de imágenes, mi fondo de armario está formado por steganabara y stegsolve. Si con esas dos herramientas no lo veo claro ya empiezo a mirar en sus entrañas y en este caso es justo lo que hace falta, mirar en su interior.
La víctima
imagen original del reto
Estamos ante una imagen GIF de 6,36KB (6513 bytes) cuya resolución es 236×42. Debido a la extensión tenderemos a analizar los frames por si se trata de una animación. Una vez desestimada la animación entran en juego steganabara, stegsolve y demás familia. Si todo lo anterior falla abro el archivo con un editor hexadecimal y lo reviso manualmente por si hay algo que me llama la atención.
Bytes
Explorando el interior del archivo enseguida encontramos algo que llama la atención, una sucesión de bytes con espacios intercalados.
Tras copiar los bytes lo primero es eliminar los espacios y empezar a jugar con ellos. Una de las cosas que podemos hacer es convertir los bytes a ascii y voilá, nos encontramos con lo que parece otro archivo GIF.
Copiamos los bytes con la ayuda de nuestro editor hexadecimal favorito, guardamos el archivo como GIF y reto superado.
Este es un crackme de la web de Karpoff programado por Sotanez y realizado en Delphi. Como máximo nos deja meter nombres de 10 dígitos.
El algoritmo
Es un algoritmo muy sencillo pero veremos que nos tendremos que fijar en el DUMP de Olly para saber que demonios hace. Como de costumbre abrimos Olly y en las «Referenced Strings» localizamos la palabra «Registrado«, pinchamos en ella y localizamos la porción de código que nos interesa. Vamos a analizarla.
Vemos 3 bucles, el primero pone la memoria (Dump) a cero, el segundo guarda nuestro nombre (errata en la imagen) en el Dump y el tercero realiza la suma de los valores ascii del nombre. Hasta aquí todo bien, pero vamos a hacer una prueba para el nombre deurus.
Nombre: deurus
Serial: 64+65+75+72+75+73 = 298 (664 en decimal)
Probamos el serial en el programa y nos da error, vale, vamos a analizar más a fondo los bucles.
El primer bucle hemos dicho que pone la memoria a 0, en concreto desde «45BC60» y de 4 en 4 (fíjate en el Add 4), es decir, pone a 0 los offsets 45BC60, 45BC64, 45BC68, 45BC6C, 45BC70, 45BC74, 45BC78, 45BC7C, 45BC80, 45BC84, ya que el bucle se repite 10 veces. En la imágen queda claro.
El segundo bucle se repite 11 veces y lo que hace es guardar en el dump el valor ascii de las letras de nuestro nombre. En la imagen lo vemos.
A primera vista ya vemos un valor extraño en la posición 45BC80, y es que cuando debiera haber un 0, hay un 12. Vamos a ver como afecta esto al serial final.
El tercer bucle se repite 10 veces y lo que hace es sumar los valores que haya en el DUMP en las posiciones anteriormente citadas.
En concreto suma 64+65+75+72+75+73+0+0+12+0 = 2AA (682 en decimal). Probamos 682 como serial y funciona. Realizando más pruebas vemos que para nombres con un tamaño inferior a 5 letras se ocupan las posiciones 45BC70 y 45BC80 con valores extraños, el resto de posiciones se mantienen a 0. En las imágenes inferiores se pueden apreciar más claramente los valores extraños.
Nombre de tamaño < 5.
Nombre de tamaño >5 y <9
Nombre de tamaño = 10
En resumen:
Nombre de tamaño < 5 –> Ascii SUM + 14h Nombre de tamaño >5 y <9 –> Ascii SUM + 12h Nombre de tamaño =10 –> Ascii SUM
Con esto ya tenemos todo lo que necesitamos para nuestro keygen.
char Nombre[11];
GetWindowText(hwndEdit1, Nombre, 11);
char Serial[20];
int len = strlen(Nombre);
int suma = 0;
for(int i = 0; i <= len; i = i + 1)
{
suma += Nombre[i];
}
if(len < 5){
suma +=0x14;
}
if(len > 5 && len < 9){
suma +=0x12;
}
wsprintf(Serial,"%d",suma);
SetWindowText(hwndEdit2, TEXT(Serial));
Este crackme pertenece a la página de Karpoff Spanish Tutor. Data del año 2000 y está realizado en «Borland Delphi 6.0 – 7.0», además, para resolverlo deberemos activar un botón y conseguir la clave de registro. La principal dificultad proviene a la hora de activar el botón ya que el serial es en realidad un serial hardcodeado muy sencillo.
Activar un botón en memoria
Existen numerosas herramientas para facilitarnos esta tarea, una de las más conocidas en el entorno del Cracking es «Veoveo» realizado por Crack el Destripador & Marmota hace ya unos añitos. Con el crackme ejecutado, ejecutamos VeoVeo y nos aparece el icono en la barra de tareas, hacemos click derecho y elegimos Activar Botones (manual) y ya tenemos el botón activado. Claro está que en cada ejecución del Crackme debemos de Re-activarlo.
Activar el botón de forma permanente
Lo que siempre nos interesa es que el botón esté activado de forma permanente y eso nos exige un poco más de atención. En este caso nos enfrentamos a Delphi y no nos sirve ni Resource Hacker ni Dede. Cuando nos encontramos en un punto muerto el último recurso siempre es realizar un programa en Delphi con un botón activado y otro desactivado y compararlos con un editor hexadecimal para saber que cambia. Si hacemos esto llegaremos a la conclusión de que en Delphi el bit que equivale a desactivado es 8 y ha activado es 9. Con este simple cambio ya tenemos el crackme parcheado. Comentar que en este caso el crackme no tiene ningún timer ni ninguna rutina que desactive el botón de forma periódica, este es el caso más simple.
Serial Hardcodeado
Abrimos Ollydbg y en las «String references» encontramos los mensajes de versión registrada, pinchamos sobre ellos y vemos a simple vista la zona de comprobación del serial. Como podéis observar, el serial se vé a simple vista.
0045811A |. B8 10824500 MOV EAX,CrackMe3.00458210 ; ASCII "ESCRIBE ALGO JOER"
0045811F |. E8 D889FDFF CALL CrackMe3.00430AFC
00458124 |. EB 5C JMP SHORT CrackMe3.00458182
00458126 |> 807D FF 4F CMP BYTE PTR SS:[EBP-1],4F - O
0045812A |. 75 56 JNZ SHORT CrackMe3.00458182
0045812C |. 807D FE 41 CMP BYTE PTR SS:[EBP-2],41 - A
00458130 |. 75 50 JNZ SHORT CrackMe3.00458182
00458132 |. 807D FD 45 CMP BYTE PTR SS:[EBP-3],45 - E
00458136 |. 75 4A JNZ SHORT CrackMe3.00458182
00458138 |. 807D FC 4B CMP BYTE PTR SS:[EBP-4],4B - K
0045813C |. 75 44 JNZ SHORT CrackMe3.00458182
0045813E |. 807D FB 43 CMP BYTE PTR SS:[EBP-5],43 - C
00458142 |. 75 3E JNZ SHORT CrackMe3.00458182
00458144 |. 807D FA 41 CMP BYTE PTR SS:[EBP-6],41 - A
00458148 |. 75 38 JNZ SHORT CrackMe3.00458182
0045814A |. 807D F9 52 CMP BYTE PTR SS:[EBP-7],52 - R
0045814E |. 75 32 JNZ SHORT CrackMe3.00458182
00458150 |. 807D F8 4B CMP BYTE PTR SS:[EBP-8],4B - K
00458154 |. 75 2C JNZ SHORT CrackMe3.00458182
00458156 |. 807D F7 20 CMP BYTE PTR SS:[EBP-9],20 -
0045815A |. 75 26 JNZ SHORT CrackMe3.00458182
0045815C |. 807D F6 49 CMP BYTE PTR SS:[EBP-A],49 - I
00458160 |. 75 20 JNZ SHORT CrackMe3.00458182
00458162 |. 807D F5 4F CMP BYTE PTR SS:[EBP-B],4F - O
00458166 |. 75 1A JNZ SHORT CrackMe3.00458182
00458168 |. 807D F4 54 CMP BYTE PTR SS:[EBP-C],54 - T
0045816C |. 75 14 JNZ SHORT CrackMe3.00458182
0045816E |. 807D F3 20 CMP BYTE PTR SS:[EBP-D],20 -
00458172 |. 75 0E JNZ SHORT CrackMe3.00458182
00458174 |. 807D F2 41 CMP BYTE PTR SS:[EBP-E],41 - A
00458178 |. 75 08 JNZ SHORT CrackMe3.00458182
0045817A |. 807D F1 59 CMP BYTE PTR SS:[EBP-F],59 - Y
0045817E |. 75 02 JNZ SHORT CrackMe3.00458182
00458180 |. B3 01 MOV BL,1
00458182 |> 80FB 01 CMP BL,1
00458185 |. 75 4C JNZ SHORT CrackMe3.004581D3
00458187 |. BA 2C824500 MOV EDX,CrackMe3.0045822C
0045818C |. 8B86 F4020000 MOV EAX,DWORD PTR DS:[ESI+2F4]
00458192 |. E8 B5EBFDFF CALL CrackMe3.00436D4C
00458197 |. BA 48824500 MOV EDX,CrackMe3.00458248 ; ASCII "VERSION REGISTRADA :)"
Serial = YA TOI KRACKEAO
Hoy vamos a enfrentarnos a cuatro retos de esteganografía relativamente sencillos, y digo relativamente, debido a que hay tantas formas de esconder información en un archivo, ya sea imagen, vídeo o sonido, que afrontarlos suele ser desesperante. Las cuatro imágenes son aparentemente las mismas que la que se ve en portada.
Una buena práctica cuando te enfrentas a retos stego de tipo imagen es realizar una búsqueda inversa. Una búsqueda inversa consiste en buscar la imagen original mediante buscadores especializados como TinEye o Google. Si conseguimos la imagen original podemos resolver el reto simplemente comparando o nos puede dar una idea del tipo de modificación por su diferencia de tamaño, colores, degradados, etc.
Stego 1
Descargamos la imagen del reto. Se trata de una imagen JPEG de 526×263 y 76.6 KB (78445 bytes). Su hash SHA1 es «89aed5bbc3542bf5c60c4c318fe99cb1489f267a«
Realizamos una búsqueda inversa de la imagen y encontramos sin dificultad la imagen original mediante TinEye.
Por lo que vemos ha cambiado el tamaño de 78447 bytes a 78445 bytes y su hash SHA1 tampoco coincide obviamente, lo que nos confirma que ha sufrido alguna modificación. Echando un vistazo con un editor hexadecimal te puedes volver loco por lo que vamos a realizar una comparación mediante la herramienta online DiffNow.
Al realizar la comparación sale a relucir lo que buscamos. La clave es una simple cadena de texto.
Stego 2
Lo primero es realizar de nuevo la comparación.
Imagen
Tamaño
SHA1
Original
78447 bytes
8924676317077fc07c252ddeec04bd2a0ecfdda4
imagen2.jpeg
116386 bytes
7641e3906f795c137269cefef29f30fcb9cb1b07
Como vemos, la imagen ha aumentado significativamente, de 76,6 KB a 113 KB. Cuando el aumento de tamaño llama la atención normalmente tenemos otro archivo insertado. Lo primero que suelo hacer yo es fijarme si ha sido modificado el final del archivo con un editor hexadecimal. Los bytes de cola de un archivo jpg/jpeg son FFD9 y en este caso no vemos modificación alguna al final del archivo. Si el archivo no está al final requiere realizar una búsqueda más exhaustiva. Para estos casos tengo una herramienta de creación propia que se llama Ancillary y que sirve para buscar cierto tipo de archivos dentro de otros como imágenes, documentos de Office, Open Office, pdf, etc. Ancillary encuentra otro jpg que es el que le daba el peso extra y que vemos a continuación. La clave es el título de la película (ojo a las mayúsculas/minúsculas).
Stego 3
El tercer reto parece que tiene algún error debido a que el archivo coincide completamente con el original. Pienso que se ha subido la imagen original por error. Se lo he comunicado al admin del dominio y si algún día obtengo respuesta actualizaré la entrada.
Imagen
Tamaño
SHA1
Original
78447 bytes
8924676317077fc07c252ddeec04bd2a0ecfdda4
imagen3.jpeg
78447 bytes
8924676317077fc07c252ddeec04bd2a0ecfdda4
Actualización 21/08/2016
Al parecer, la solución de este reto es llegar a la conclusión de que la imagen no está modificada. La respuesta del Administrador de la web así lo confirma.
desingsecurity [at] gmail [dot] com – Sorry about the delay, is precisely what is intended with that challenge, they can determine if the image is changed or not , the challenge was solved you . We’ll be equal way improving this point.
Greetings and Thanks
Stego 4
Lo primero es realizar de nuevo la comparación.
Imagen
Tamaño
SHA1
Original
78447 bytes
8924676317077fc07c252ddeec04bd2a0ecfdda4
imagen4.jpeg
93174 bytes
a6329ea4562ef997e5afd067f3b53bdab4665851
Al igual que en el caso dos el tamaño ha aumentado significativamente de modo que miramos al final del archivo y esta vez si vemos que hay insertado unos bytes tras el final del jpg (recordemos FFD9)
El archivo tiene pinta de ser una hoja de cálculo de Open Office o Libre Office según indica la palabra «spreadsheet«. Lo abrimos con Excel y tras analizar la maraña de datos enseguida vemos una clave que llama la atención.
Warning: This challenge is still active and therefore should not be resolved using this information. Aviso: Este reto sigue en activo y por lo tanto no se debería resolver utilizando esta información.
Realistic Challenge 3: Your school is employing a web designer who is charging far too much for site design and doesn’t know anything about protecting the site. However, he’s sure that there’s no way anyone can hack into any site he’s designed, prove him wrong!
En tu escuela están haciendo una web nueva muy rápido. El creador asegura que no le pueden hackear, demuéstrale que está equivocado.
Analizando a la víctima
Echamos un vistazo y vemos en el menú cosas interesantes. La primera de ellas es un Login que pronto descartamos ya que no parece llevar a ninguna parte. La segunda sirve para mandar enlaces al administrador y que este los publique posteriormente en la web.
Vamos a trastear un poco con la opción de mandar enlaces. En el código fuente ya vemos algo interesante y es que hay un campo oculto con el valor a 1 al mandar el enlace. Probamos a mandar un enlace sin tocar nada y nos dice que lo manda pero que lo tienen que aprobar. Vamos a probar ahora cambiando el valor del parámetro oculto a 0 con Firebug.
¡Funcionó!, el enlace ha pasado el filtro.
¿Cómo podemos aprovechar esto?, pués la forma más común es «XSS cross site scripting«. Veamos una prueba. Con el parámetro oculto otra vez en 0 mandamos el siguiente enlace y reto superado.
Con The Ring inauguro una nueva sección llamada Blooper Tech Movie (BTM), algo así como pifias o tomas falsas tecnológicas en películas. Aunque no os lo creáis, los creadores del séptimo arte y sus asesores son humanos, y como tal se rigen por la ley del mínimo esfuerzo. En este BTM vamos a ver como una simple escena nos puede arruinar la excelente atmósfera de intriga que hasta ese momento se respiraba.
BTM
Transcurridos 70 minutos de película vemos que la protagonista está en una redacción buscando información sobre la maldita cinta de vídeo en un PC.
Hasta aquí todo correcto, pero instantes después vemos que realiza una búsqueda sobre «Moesko Islands» y cuando se abre el plano y podemos ver la barra de direcciones, en realidad vemos un archivo local situado en «C:\WIN98\Desktop\search.com\2_moesko_island_pt2.html«. A continuación la secuencia, se pueden ver los enlaces «locales» en el segundo 13 y 17.
Teniendo en cuenta que la película data del año 2002, me parece increíble que los productores no se lo curraran un poco más y registraran un dominio como «jdoesearch.com» y simularan que se realizan las búsquedas ONline y no OFFline como se están haciendo en realidad.
Quizá no tenían pensado mostrar la parte superior del navegador o simplemente pensaron que nadie se fijaría pero el caso es que para cualquiera que haya navegado por Internet más de 2 veces, si se fija en la barra de direcciones su expresión facial cambia a WTF!.
Warning: This challenge is still active and therefore should not be resolved using this information. Aviso: Este reto sigue en activo y por lo tanto no se debería resolver utilizando esta información.
En los retos de esteganografía ya uno se espera de todo, y cuantos más haces más enrevesados encuentras. Hoy no, hoy vamos a tratar un clásico dentro de este tipo de retos, ocultar un archivo dentro de otro.
Buscando la solución
Prácticamente lo primero que hago cuando me descargo una imágen en éste tipo de retos es abrirla con un editor hexadecimal, y en este caso hemos dado en el clavo. La abrimos con un editor cualquiera y al final del archivo encontramos que estamos tratando con un archivo ZIP (cabecera PK).
La abrimos con 7zip y vemos el prometido archivo txt, dentro ¿qué abrá?
Toda esta aventura comienza con un archivo llamado pretty_raw, sin extensión. Porque sí. Porque las extensiones son una invención heredada de CP/M, precursor de MS-DOS, que Windows terminó de popularizar. Porque son innecesarias. Y porque echo de menos cuando los archivos se reconocían por sus permisos… y no por cómo se llamaban.
Como iba diciendo, todo esto comienza mediante el análisis de pretty_raw. Mirando debajo de la falda con un editor hexadecimal encontramos unos cuantos bytes aleatorios hasta dar con una cabecera PNG.
Si atendemos a la captura, justo antes de la cabecera PNG tenemos 116.254 bytes (0x1C61E). Tomad nota que este número será relevante más adelante.
Extraemos el PNG, lo visualizamos y lo pasamos por todas las herramientas habidas y por haber. Nada funciona. Volvemos a visualizarlo con atención y vemos que hace referencia a un archivo llamado flag.png con unas dimensiones que no coinciden con la extraída.
Toca centrarse y pensar en que camino tomar. Hemos gastado tiempo con el PNG extraído y quizá lo mejor sea centrarse en los bytes que inicialmente hemos descartado. En concreto se trata de un bloque de 116.254 bytes, pero espera, 1570×74=116.180 bytes. ¡Mierda!, no coincide exactamente con los bytes extraídos. Bueno, da igual. Si suponemos que el PNG que buscamos no tiene compresión y que cada pixel ocupa un byte (escala de grises y 8 bits), su tamaño depende únicamente de la geometría y de cómo se almacenan las filas en memoria. Vamos a procesarlo con Python para salir de dudas.
import numpy as np
from PIL import Image
INPUT_FILE = "pretty_raw"
OUTPUT_FILE = "pretty_raw_flag.png"
WIDTH = 1570 # ¿estás seguro?
HEIGHT = 74
DEPTH = 8 # bits
# Leer archivo como RAW
with open(INPUT_FILE, "rb") as f:
raw = f.read()
expected_size = WIDTH * HEIGHT
if len(raw) < expected_size:
raise ValueError("El archivo no tiene suficientes datos")
# Convertir a array numpy (grayscale 8 bits)
img = np.frombuffer(raw[:expected_size], dtype=np.uint8)
img = img.reshape((HEIGHT, WIDTH))
# Crear imagen
image = Image.fromarray(img, mode="L")
image.save(OUTPUT_FILE)
print(f"Imagen generada correctamente: {OUTPUT_FILE}")
El script nos devuelve un PNG válido pero con las letras torcidas. Tras darle vueltas me di cuenta de que si en el script usamos como WIDTH=1571 en lugar de 1570, la imagen resultante es correcta y tiene todo el sentido del mundo ya que 1571×74=116.254, que son exactamente los bytes que se encuentran antes del png señuelo.
Aunque el ancho visible de la imagen es de 1570 píxeles, cada fila ocupa realmente 1571 bytes. Ese byte adicional actúa como relleno (padding) y forma parte del stride o bytes por fila. Ignorar este detalle lleva a un desplazamiento erróneo acumulativo y por eso se ve la imagen torcida. En este caso concreto da igual ya que el texto se aprecia, pero si el reto hubiera sido más exigente no se vería nada.
Acabo de montar AperiSolve en una Raspi que tenía por casa pensando que sería coser y cantar, pero me he encontrado con que el repositorio no estaba preparado para todas las distros Linux de forma estándar. El resultado lo he colgado en Github, de modo que para montarlo en vuestra propia Raspi solo tenéis que seguir estos pasos:
1. Clonar el repositorio
git clone https://github.com/deurus/AperiSolve-Raspi3.git
cd AperiSolve-Raspi3/AperiSolve
2. Construir los contenedores
docker compose build
docker compose up -d
3. Abrir la web
http://<IP_RASPI>:5000
Si tenéis curiosidad de la adaptación que he tenido que hacer aquí están los pasos que he seguido:
1. Preparar el sistema
sudo apt update
sudo apt install -y git docker.io docker-compose
sudo usermod -aG docker $USER
newgrp docker
2. Clonar AperiSolve
git clone https://github.com/Zeecka/AperiSolve.git
cd AperiSolve
3. Crear la estructura de build para la imagen ARM/x86
nano docker-compose.yml
y pega este contenido:
FROM python:3.11-slim
RUN apt-get update && apt-get install -y \
zip \
p7zip-full \
binwalk \
foremost \
exiftool \
steghide \
ruby \
binutils \
pngcheck \
&& rm -rf /var/lib/apt/lists/*
COPY aperisolve/ /aperisolve/
RUN pip install --no-cache-dir -r /aperisolve/requirements.txt
WORKDIR /aperisolve
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "wsgi:app"]
4. Arreglar docker-compose.yml para ser válido y compatible
services:
web:
image: aperisolve-local
build: .
container_name: aperisolve-web
ports:
- "5000:5000"
depends_on:
- redis
- postgres
environment:
DB_URI: "postgresql://aperiuser:aperipass@postgres:5432/aperisolve"
worker:
image: aperisolve-local
container_name: aperisolve-worker
depends_on:
- redis
- postgres
environment:
DB_URI: "postgresql://aperiuser:aperipass@postgres:5432/aperisolve"
redis:
image: redis:7
container_name: aperisolve-redis
postgres:
image: postgres:16
container_name: aperisolve-postgres
environment:
POSTGRES_USER: aperiuser
POSTGRES_PASSWORD: aperipass
POSTGRES_DB: aperisolve
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
5. Modificar aperisolve/config.py
nano config.py
y pega este contenido:
from pathlib import Path
IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".tiff"]
WORKER_FILES = ["binwalk", "foremost", "steghide", "zsteg"]
RESULT_FOLDER = Path(__file__).parent.resolve() / "results"
RESULT_FOLDER.mkdir(parents=True, exist_ok=True)
6. Modificación de aperisolve/app.py
Sustituir la línea: app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("DB_URI")
por:
default_db = "postgresql://aperiuser:aperipass@postgres:5432/aperisolve"
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("DB_URI", default_db)
7. Construir la imagen
docker build -t aperisolve-local .
8. Levantar los contenedores
docker compose down
docker compose up -d
9. Comprobar logs
docker logs aperisolve-web --tail=50
docker logs aperisolve-worker --tail=50
10. Acceder a la web
- Desde cualquier máquina de la red local: http://IP-DE-LA-MAQUINA:5000
- Desde la Raspi: http://localhost:5000
11. Limpieza (cuando necesites)
- Reiniciar contenedores:
docker compose restart
- Borrar resultados antiguos:
sudo rm -r aperisolve/results/*