Quantcast
Channel: El blog de García Larragan y Cía
Viewing all 639 articles
Browse latest View live

Criptografía (CXXVI): Reto 26

$
0
0
Otro reto de dificultad media sobre criptografía, relacionado con el anterior y, por tanto, en el que también se ve involucrado el criptosistema RSA.

Como siempre, se admiten soluciones en forma de comentarios a esta entrada. Pasado un tiempo iré proporcionando pistas para su resolución, un máximo de tres, y posteriormente actualizaré este post con la solución.

Reto 26: "El tamaño sí importa (III)".

Y como no hay dos sin tres, otro reto en el que se nos viene a recordar que el tamaño sí importa. Supón que interceptas tres criptogramas, que sabes que se corresponden con un mismo texto en claro (m) y que dichos criptogramas han sido enviados a tres personas diferentes, ¿puedes obtener el texto en claro (m)?.

Nota: en el archivo asociado al reto se indican los valores en decimal correspondientes al módulo (n) y al exponente público (e) de las respectivas claves de las tres personas y los valores en decimal de los criptogramas remitidos a éstas.

Dificultad:
Tipo:           Criptografía.

Recursos:   reto_26.txt.

******** __/__/____
Pista 1:     Por publicar.

******** __/__/____
Solución:  Por publicar.

******** PRÓXIMO RETO
Reto 27:   Por publicar.

Gimnasia mental (XXXII): los siete ladrones

$
0
0
Siete ladrones roban un cargamento de lingotes de oro y a la hora de repartir el botín a partes iguales sobran 6 lingotes.

Como no se ponen de acuerdo en la forma de repartir los lingotes sobrantes, se pelean entre ellos y mueren dos ladrones. Tras realizar de nuevo el reparto entre los supervivientes sobran dos lingotes, por lo que se vuelve a desatar una pelea entre ellos y muere otro ladrón. Ahora sí, el reparto de los lingotes se realiza a partes iguales entre los ladrones que quedan vivos.

a) ¿Cuál es el número mínimo de lingotes que han robado los ladrones para que pueda ser cierto el enunciado?.
b) ¿Cuál es el siguiente número de lingotes que podría hacer cierto el enunciado?. 

Si eres capaz de resolver este problema, sin duda, podrás resolver también este reto de criptografía.


Solución: utilizo lo que he entendido del teorema chino del resto para resolver este problema.

Tenemos el siguiente sistema de congruencias lineales simultáneas:

X ≡ 6 (mod 7)
X ≡ 2 (mod 5)
X ≡ 0 (mod 4)

Es decir, buscamos el número X que dividido entre 7 da como resto 6, que dividido entre 5 da como resto 2 y que dividido entre 4 da como resto 0.

Como los módulos son primos entre sí dos a dos, es decir, el máximo común divisor de ellos tomados dos a dos es igual a 1, podemos utilizar el teorema chino del resto para resolverlo:

mcd(7, 5) = 1
mcd(7, 4) = 1
mcd(5, 4) = 1

Lo primero que tenemos que hacer para calcular X es obtener el módulo (N), que sería el producto de los módulos de las tres congruencias:

N = 7 * 5 * 4 = 140

Lo segundo que tenemos que hacer es dividir el nuevo módulo (N) entre los módulos de las tres congruencias:

N1 = 140 / 7 = 20
N2 = 140/ 5 = 28
N3 = 140 / 4 = 35

Lo tercero que tenemos que hacer es calcular el inverso mutiplicativo modular de los valores obtenidos en el paso anterior, es decir:

20 * s1≡ 1 (mod 7)
28 * s2≡ 1 (mod 5)
35 * s3≡ 1 (mod 4)

Cuando los números son pequeños, como es el caso, podemos utilizar la fuerza bruta para calcular el inverso multiplicativo modular, es decir, basta con ir probando todos los números (1, 2, …) uno detrás de otro hasta encontrarlo.

s1= 6
s2= 2
s3= 3

Finalmente encontramos la solución particular:

6 * 20 * 6 + 2 * 28 * 2 + 0 * 35 * 3 = 720 + 112 + 0 = 832

832 X (mod 140)

X = 132

Y la solución general sería:

X = 132 + 140 Y

Para Y = 1: X = 132 + 140 * 1= 272
Para Y = 2: X = 132 + 140 * 2= 412

Por tanto, la respuesta a la primera pregunta es que los ladrones han robado como mínimo 132 lingotes de oro y a la segunda, es decir, el siguiente número de lingotes robados que podría hacer cierto el enunciado es 272.


Este mismo teorema se puede utilizar para resolver también este reto de criptografía.

Dejo un pequeño script en python que resuelve el problema planteado en este post y que puede servir con pequeñas variaciones para resolver el citado reto de criptografía.

import gmpy2

a1 = 6
n1 = 7
a2 = 2
n2 = 5
a3 = 0
n3 = 4

N = n1*n2*n3
X = (a1 * N/n1 * gmpy2.invert(N/n1,n1) + a2 * N/n2 * gmpy2.invert(N/n2,n2) + a3 * N/n3 * gmpy2.invert(N/n3,n3)) % N

print ''
print '[+] Respuesta pregunta 1: los ladrones han robado como minimo', X, 'lingotes de oro.'
print ''
print '[+] Respuesta pregunta 2: el siguiente numero de lingotes robados que podria hacer cierto el enunciado es', X+N,'.'

Criptografía (CXXVII): Solución Reto 26

$
0
0
El  enunciado del reto de criptografía que formulé en este post era el siguiente:

"Y como no hay dos sin tres, otro reto en el que se nos viene a recordar que el tamaño sí importa. Supón que interceptas tres criptogramas, que sabes que se corresponden con un mismo texto en claro (m) y que dichos criptogramas han sido enviados a tres personas diferentes, ¿puedes obtener el texto en claro (m)?.

Nota: en el archivo asociado al reto se indican los valores en decimal correspondientes al módulo (n) y al exponente público (e) de las respectivas claves de las tres personas y los valores en decimal de los criptogramas remitidos a éstas".


Solución: Decía en la primera pista que puse para ayudar a resolver este reto que como en el enunciado se establece que los tres criptogramas se corresponden con un mismo texto en claro (m1 = m2 = m3 = m) y en el archivo asociado al reto (reto_26.txt) vemos que los tres exponentes públicos son iguales a 3 (e1 = e2 = e3 = 3), si los módulos (n1, n2, n3) son primos entre sí, coprimos o primos relativos dos a dos, es decir, si el mcd(ni, nj) = 1 para todo i distinto de j, entonces quizás el teorema chino del resto pueda ayudarnos a calcular m.

Por tanto, en primer lugar lugar creo un script en python para para comprobar si los módulos (n1, n2 y n3) son coprimos dos a dos, es decir, si el máximo común divisor de ellos tomados de dos en dos es igual a 1:

import gmpy2

n1 = gmpy2.mpz("141237893326188574204627692926236597832923906875563312407296586270187098500830977089086198296698274977259945744931269328582013379088128599997921784764795876512664274811704263719810554438911212375275389637307533676792707072305650179551138270208913948697352662887831274626962574428552932203244712293494591555511")
n2 = gmpy2.mpz("123540448208108190442413152518982095069440783769340903005935077787457590335172518758985701753251638030875882695636036396466435248787871355571628582178196705443476117178179250809139797395693806996196479271907692595838139580204715620363875721843078735275575567535802910937573265882011978734919390275028927498651")
n3 = gmpy2.mpz("129764663185510756371105783443119016055267527773132919526587344357374102887540763282339924512254838799749899518844225166115902370305244996180768430247896711901844141296135907974189994271244431528747412109545908983450918905491684446582713912788850397046516711871065128208507799172858171575837210017773451264069")

print ''
if gmpy2.gcd(n1, n2)!=1 or gmpy2.gcd(n1, n3)!=1 or gmpy2.gcd(n2, n3)!=1:
   print '[-] Los modulos n1, n2 y n3 no son primos entre si, coprimos o primos relativos dos a dos.'
else:
   print '[+] Los modulos n1, n2 y n3 son primos entre si, coprimos o primos relativos dos a dos.'

Tras ejecutar este script, podemos afirmar que, efectivamente, los módulos de las claves son coprimos dos a dos:
Y, por tanto, el teorema chino del resto nos puede ayudar a obtener el texto en claro (m) que se corresponde con los tres criptogramas (c1, c2 y c3) que se enviaron, respectivamente, a las tres personas.

En este caso, como el texto en claro correspondiente a los tres criptogramas es el mismo (m1 = m2 = m3 = m) y los exponentes públicos de las tres claves son iguales a 3 (e1 = e2 = e3 = 3), el cifrado se ha realizado de la siguiente manera:

c1 = m^3 mod n1
c2 = m^3 mod n2
c3 = m^3 mod n3

Por tanto, tengo el siguiente sistema de congruencias simultáneas:

m^3   c1 mod n1
m^3 ≡ c2 mod n2
m^3 ≡ c3 mod n3

Es decir, se trata de hallar un número (m^3) que dividido entre n1 dé como resto c1, que dividido entre n2 dé como resto c2 y que dividido entre n3 dé como resto c3, y esto puede resolverse utilizando el teorema chino del resto (ver ejemplo en este post).

Una vez que calculo ese número (m^3), lógicamente, el mensaje en claro (m) es la raíz cúbica del mismo.

Para obtener m creo el siguiente script en python:

import gmpy2
import binascii

e = 3
c1 = gmpy2.mpz("37050595107607215425728862670311169686501715648529655096852022500961331010583163575464647323975465331340563969499827140101447214847555787027687021816122499851981434087708434429330112796102295773537370473348683587834276525448296670045256125366626288277440736754846403151338632769822829343206702099866719381340")
n1 = gmpy2.mpz("141237893326188574204627692926236597832923906875563312407296586270187098500830977089086198296698274977259945744931269328582013379088128599997921784764795876512664274811704263719810554438911212375275389637307533676792707072305650179551138270208913948697352662887831274626962574428552932203244712293494591555511")
c2 = gmpy2.mpz("29448590570339337019846906819372551561193767416448894862618308297222578491963152380884360258445115263084004692906639292433283570505453988598060990964248465596120682287756617715751129758488463897763244247700203105329378047389157194907729114086283485641777376575040271414336675879220483713989496496481639193284")
n2 = gmpy2.mpz("123540448208108190442413152518982095069440783769340903005935077787457590335172518758985701753251638030875882695636036396466435248787871355571628582178196705443476117178179250809139797395693806996196479271907692595838139580204715620363875721843078735275575567535802910937573265882011978734919390275028927498651")
c3 = gmpy2.mpz("59230107217036032019940650579150530277946966690389133886396754469356281025902289554077581971768082168768214384534406164927205095184494909622994804896955185141556382755419648502790626246194221204969871419277083672288347576620879291907671233882681829121454526629083136356495332711948312285534940375019766634305")
n3 = gmpy2.mpz("129764663185510756371105783443119016055267527773132919526587344357374102887540763282339924512254838799749899518844225166115902370305244996180768430247896711901844141296135907974189994271244431528747412109545908983450918905491684446582713912788850397046516711871065128208507799172858171575837210017773451264069")

N = n1*n2*n3
mcubo = (c1 * N/n1 * gmpy2.invert(N/n1,n1) + c2 * N/n2 * gmpy2.invert(N/n2,n2) + c3 * N/n3 * gmpy2.invert(N/n3,n3)) % N

m, exact = gmpy2.iroot(mcubo,e)
if exact:
   print ''
   print '[+] Texto en claro (m) ............', binascii.unhexlify(gmpy2.digits(m,16))

Y tras ejecutar este script:
Ya puedo ver la solución al reto: H4574D5 BR04DC457 4774CK.

Criptografía (CXXVIII): Reto 27

$
0
0
En esta ocasión un reto de dificultad media sobre criptografía en el que se ve involucrado un criptosistema clásico inventado a finales de la década de los años veinte del siglo pasado por un matemático y criptógrafo estadounidense.

Como siempre, se admiten soluciones en forma de comentarios a esta entrada. Pasado un tiempo iré proporcionando pistas para su resolución, un máximo de tres, y posteriormente actualizaré este post con la solución.

Reto 27: "Un poco de ti es mucho".

Como en otros retos que he puesto en este blog, la persona que aparece en la imagen que ilustra este post nos dirá qué criptosistema se ve involucrado, lo que además confirmaremos con la pista dada al inicio. Una vez que hayas averiguado lo anterior, supón que interceptas el siguiente criptograma:
"ZVMVCWQKXVUNPOHOKUOJTSZLGIULKCBQALFQFGBFILXIBWGC",
y que además sabes o infieres que el inicio del mismo se corresponde con el siguiente texto en claro: “THESOLUTION”.¿Puedes descifrar el criptograma y obtener así la solución de este reto?.

Dificultad:
Tipo:           Criptografía.

******** 12/02/2019
Pista 1:     Ya sabrás que la persona que aparece en la imagen que ilustra este post es el matemático y criptógrafo estadounidense Lester S. Hill, inventor en 1929 del conocido como cifrado de Hill. Este criptosistema de sustitución poligráfica era muy robusto, pero tenía una vulnerabilidad importante: su "talón de Aquiles" eran los ataques con texto claro conocido, y precisamente de esto es de lo que se trata en este reto.

******** __/__/____
Solución:  Por publicar.

******** PRÓXIMO RETO
Reto 28:   Por publicar.

Criptografía (CXXIX): Solución Reto 27

$
0
0
El  enunciado del reto de criptografía que puse en este post era el siguiente:

"Como en otros retos que he puesto en este blog, la persona que aparece en la imagen que ilustra este post nos dirá qué criptosistema se ve involucrado, lo que además confirmaremos con la pista dada al inicio. Una vez que hayas averiguado lo anterior, supón que interceptas el siguiente criptograma:
"ZVMVCWQKXVUNPOHOKUOJTSZLGIULKCBQALFQFGBFILXIBWGC",
y que además sabes o infieres que el inicio del mismo se corresponde con el siguiente texto en claro: “THESOLUTION”.¿Puedes descifrar el criptograma y obtener así la solución de este reto?".

SoluciónEn el enunciado decía que si sabes o averiguas quién es la persona que aparece en la imagen que ilustra este post entonces también sabrás el criptosistema involucrado en este reto. Pues bien, busco con 'Google imágenes' y obtengo:
Este resultado coincide con lo que se dice al inicio del post en el que se enunció este reto respecto a que el criptosistema involucrado fue inventando por un matemático americano, y la Wikipedia nos cuenta que entre las notables aportaciones de Lester S. Hill se encuentra el conocido comocifrado de Hill.

Una vez que hemos averiguado que el texto en claro se ha cifrado utilizando el criptosistema ideado por Hill en 1929 y suponiendo, por lo que conocemos de él y por el criptograma, que está escrito en inglés y que el alfabeto utilizado se reduce a las letras mayúsculas de dicho idioma (A = 0, B = 1, C = 2, ..., Z = 25), implemento el ataque de 3 maneras diferentes:

1.- Mediante "lápiz y papel", es decir, manualmente.
2.- Utilizando una herramienta especializada.
3.- Creando un pequeño script en python para resolver el reto.

Pero antes que nada recordar que el cifrado de Hill funciona multiplicando una matriz (K) cuadrada de orden k (k filas y k columnas), que se utiliza como clave para cifrar, por cada uno de los bloques de longitud k del texto en claro (M), mientras que el descifrado se realiza multiplicando la matriz inversa de la que se ha empleado como clave en el cifrado (K−1)  por cada uno de los bloques de longitud k del mensaje cifrado o criptograma (C).

Tanto el cifrado como el descifrado se producen en el módulo (n) correspondiente al tamaño del alfabeto (en nuestro caso el número de caracteres en mayúsculas del idioma inglés, es decir, 26).

Dicho lo anterior, si conocemos parte del texto en claro y su correspondiente texto cifrado, siempre que podamos conocer el alfabeto utilizado, es decir, el módulo (n), y determinar el orden (k) de la matriz cuadrada empleada como clave (K), o lo que es lo mismo el tamaño del bloque (número de símbolos del texto en claro / criptograma que se cifran / descifran de forma simultánea - bigramas, trigramas, etc.), a partir de una matriz con el texto claro conocido (M) podemos calcular la matriz K−1 con la que descifrar el criptograma (C), de la siguiente manera:
C = K M
K = M-1 C
K-1 sería la matriz de descifrado

1.- Solución manual ("Lápiz y papel"):

Tal y como he dicho, para implementar este ataque es necesario conocer el orden (k) de la matriz cuadrada empleada como clave (K)o, lo que es lo mismo, el tamaño del bloque, y sabemos que éste tiene que ser un factor del número de caracteres del texto cifrado (C). Es decir, en nuestro caso 48, tamaño del criptograma, tiene que ser múltiplo de k. Así que nuestros posibles valores de tamaño de bloque son: 2, 3, 4, 6, 8, 12, 16, 24, 48.

También sabemos que, debido a que la matriz utilizada como clave (K) es cuadrada (k x k), para que este ataque pueda prosperar necesitamos tener al menos k2 letras de texto plano conocido (en nuestro caso conocemos 11 caracteres, “THESOLUTION”). Por tanto:
22 = 4; 4 < 11
32 = 9; 9 < 11
42 = 16; 16 > 11
...
Esto significa que para que podamos tener éxito con este ataque, se debe haber utilizado un tamaño de bloque de 2 ó 3, ya que si éste fuera mayor no conoceríamos los suficientes caracteres del texto en claro para resolver este reto y esto no tendría ninguna gracia :).

Cuatro caracteres como clave me parecen pocos, por lo que voy a suponer inicialmente que la clave utilizada tiene 9 caracteres (3 x 3), y si no consigo nada probaré con 4.

Ya que estamos trabajando con una matriz de 3 × 3, nuestro texto claro conocido, "THESOLUTION", se reduce a una longitud de bloque 9 (3 x 3): "THESOLUTI".

Por tanto, la matriz (M) con el texto claro conocido sería:
O utilizando la posición relativa de cada uno de esos caracteres en el alfabeto inglés ((A = 0, B = 1, C = 2, ..., Z = 25):
Lo primero que hay que hacer es obtener la matriz inversa (M-1) de la matriz con el texto claro conocido (M). Para ello utilizo la siguiente fórmula:
Donde:
M-1: matriz inversa de M (matriz con el texto claro conocido).
CT: matriz de cofactores de M transpuesta.
|M|: determinante de M, que debe calcularse en mod n (tamaño del alfabeto), en nuestro caso en módulo 26.
(|M|)-1: inverso del determinante de la matriz M. Debe calcularse en mod (tamaño del alfabeto), en nuestro caso en módulo 26.

Por tanto:
Por otra parte, la matriz (C) con la parte del criptograma correspondiente al texto claro conocido sería:
O utilizando la posición relativa de cada uno de esos caracteres en el alfabeto inglés (A = 0, B = 1, C = 2, ..., Z = 25):
Ahora calculamos (M-1 C) mod 26:
(11 x 25 + 24 x 21 +   7 x 16) mod 26 = (275 + 504 + 112) mod 26 = 891 mod 26 = 7
(11 x 21 + 24 x   2 +   7 x 10) mod 26 = (231 +   48 +   70) mod 26 = 349 mod 26 = 11
(11 x 12 + 24 x 22 +   7 x 23) mod 26 = (132 + 528 + 161) mod 26 = 821 mod 26 = 15
(8   x 25 + 24 x 21 + 15 x 16) mod 26 = (200 + 504 + 240) mod 26 = 944 mod 26 = 8
(8   x 21 + 24 x   2 + 15 x 10) mod 26 = (168 +   48 + 150) mod 26 = 366 mod 26 = 2
(8   x 12 + 24 x 22 + 15 x 23) mod 26 = (  96 + 528 + 345) mod 26 = 969 mod 26 = 7
(12 x 25 + 13 x 21 + 12 x 16) mod 26 = (300 + 273 + 192) mod 26 = 765 mod 26 = 11
(12 x 21 + 13 x   2 + 12 x 10) mod 26 = (252 +   26 + 120) mod 26 = 398 mod 26 = 8
(12 x 12 + 13 x 22 + 12 x 23) mod 26 = (144 + 286 + 276) mod 26 = 706 mod 26 = 4
Y he obtenido la matriz transpuesta (KT) de la utilizada como clave (K) en el cifrado, por lo que esta última sería:
Y ahora estoy ya en disposición de calcular la matriz de descifrado, que es la inversa (K-1) de esta última matriz obtenida.
Donde:
K-1: matriz inversa de K (matriz con la clave utilizada en el cifrado).
CT: matriz de cofactores de K transpuesta.
|K|: determinante de K, que debe calcularse en mod n (tamaño del alfabeto), en nuestro caso en módulo 26.
(|K|)-1: inverso del determinante de la matriz K. Debe calcularse en mod (tamaño del alfabeto), en nuestro caso en módulo 26.


El proceso para realizar este cálculo es el mismo que el desarrollado más arriba para obtener M-1, por lo que me limito a indicar únicamente el resultado que se obtiene:
Y, finalmente, desciframos el criptograma. Como k = 3 procedo por bloques de 3 caracteres del criptograma.

Para el primer grupo de tres caracteres:
m1 = (12 x 25 +   5 x 21 + 22 x 12) mod 26 = 300 + 105 + 264 = 669 mod 26 = 19 =  T
m2 = (20 x 25 +   5 x 21 + 13 x 12) mod 26 = 500 + 105 + 156 = 761 mod 26 =   7 =  H
m3 = (11 x 25 +   5 x 21 + 12 x 12) mod 26 = 275 + 105 + 144 = 524 mod 26 =   4 =  E

Y así sucesivamente con el resto de bloques o grupos de tres caracteres del criptograma:

m4 = (12 x 21 +   5 x   2 + 22 x 22) mod 26 = 252 +   10 + 484 = 746 mod 26 = 18 =  S
m5 = (20 x 21 +   5 x   2 + 13 x 22) mod 26 = 420 +   10 + 286 = 716 mod 26 = 14 =  O
m6 = (11 x 21 +   5 x   2 + 12 x 22) mod 26 = 231 +   10 + 264 = 505 mod 26 = 11 =  L

m7 = (12 x 16 +   5 x 10 + 22 x 23) mod 26 = 192 +   50 + 506 = 748 mod 26 = 20 = U
m8 = (20 x 16 +   5 x 10 + 13 x 23) mod 26 = 320 +   50 + 299 = 669 mod 26 = 19 = T
m9 = (11 x 16 +   5 x 10 + 12 x 23) mod 26 = 176 +   50 + 276 = 502 mod 26 =   8 = I

m10 = (12 x 21 + 5 x 20 + 22 x 13) mod 26 = 252 + 100 + 286 = 638 mod 26 = 14 = O
m11 = (20 x 21 + 5 x 20 + 13 x 13) mod 26 = 420 + 100 + 169 = 689 mod 26 = 13 = N
m12 = (11 x 21 + 5 x 20 + 12 x 13) mod 26 = 231 + 100 + 156 = 487 mod 26 = 19 = T

m13 = (12 x 15 + 5 x 14 + 22 x   7) mod 26 = 180 +   70 + 154 = 404 mod 26 = 14 = O
m14 = (20 x 15 + 5 x 14 + 13 x   7) mod 26 = 300 +   70 +   91 = 461 mod 26 = 19 = T
m15 = (11 x 15 + 5 x 14 + 12 x   7) mod 26 = 165 +   70  +  84 = 319 mod 26 =   7 = H

m16 = (12 x 14 + 5 x 10 + 22 x 20) mod 26 = 168 +   50 + 440 = 658 mod 26 =   8 = I
m17 = (20 x 14 + 5 x 10 + 13 x 20) mod 26 = 280 +   50 + 260 = 590 mod 26 = 18 = S
m18 = (11 x 14 + 5 x 10 + 12 x 20) mod 26 = 154 +   50 + 240 = 444 mod 26 =   2 = C

m19 = (12 x 14 + 5 x  9 + 22 x 19) mod 26 = 168 +    45 + 418 = 631 mod 26 =  7 = H
m20 = (20 x 14 + 5 x  9 + 13 x 19) mod 26 = 280 +    45 + 247 = 572 mod 26 =  0 = A
m21 = (11 x 14 + 5 x  9 + 12 x 19) mod 26 = 154 +    45 + 228 = 427 mod 26 = 11 = L

m22 = (12 x 18 + 5 x 25 + 22 x 11) mod 26 = 216 + 125 + 242 = 583 mod 26 = 11 = L
m23 = (20 x 18 + 5 x 25 + 13 x 11) mod 26 = 360 + 125 + 143 = 628 mod 26 =   4 = E
m24 = (11 x 18 + 5 x 25 + 12 x 11) mod 26 = 198 + 125 + 132 = 455 mod 26 = 13 = N

m25 = (12 x   6 + 5 x  8 + 22 x 20) mod 26 =   72 +    40 + 440 = 552 mod 26 =   6 = G
m26 = (20 x   6 + 5 x  8 + 13 x 20) mod 26 = 120 +    40 + 260 = 420 mod 26 =   4 = E
m27 = (11 x   6 + 5 x  8 + 12 x 20) mod 26 =   66 +    40 + 240 = 346 mod 26 =   8 = I

m28 = (12 x 11 + 5 x 10 + 22 x   2) mod 26 = 132 +   50 +   44 = 226 mod 26 = 18 = S
m29 = (20 x 11 + 5 x 10 + 13 x   2) mod 26 = 220 +   50 +   26 = 296 mod 26 = 10 = K
m30 = (11 x 11 + 5 x 10 + 12 x   2) mod 26 = 121 +   50 +   24 = 195 mod 26 = 13 = N

m31 = (12 x   1 + 5 x 16 + 22 x   0) mod 26 =   12 +   80 +     0 =   92 mod 26 = 14 = O
m32 = (20 x   1 + 5 x 16 + 13 x   0) mod 26 =   20 +   80 +     0 = 100 mod 26 = 22 = W
m33 = (11 x   1 + 5 x 16 + 12 x   0) mod 26 =   11 +   80 +     0 =   91 mod 26 = 13 = N

m34 = (12 x 11 + 5 x  5 + 22 x 16) mod 26 = 132 +   25  + 352 = 509 mod 26 = 15 = P
m35 = (20 x 11 + 5 x  5 + 13 x 16) mod 26 = 220 +   25  + 208 = 453 mod 26 = 11 = L
m36 = (11 x 11 + 5 x  5 + 12 x 16) mod 26 = 121 +   25  + 192 = 338 mod 26 =   0 = A

m37 = (12 x   5 + 5 x  6 + 22 x   1) mod 26 =   60 +   30  +   22 = 112 mod 26 =   8 = I
m38 = (20 x   5 + 5 x  6 + 13 x   1) mod 26 = 100 +   30  +   13 = 143 mod 26 = 13 = N
m39 = (11 x   5 + 5 x  6 + 12 x   1) mod 26 =   55 +   30  +   12 =   97 mod 26 = 19 = T

m40 = (12 x   5 + 5 x  8 + 22 x 11) mod 26 =   60 +   40  + 242 = 342 mod 26 =   4 = E
m41 = (20 x   5 + 5 x  8 + 13 x 11) mod 26 = 100 +   40  + 143 = 283 mod 26 = 23 = X
m42 = (11 x   5 + 5 x  8 + 12 x 11) mod 26 =   55 +   40  + 132 = 227 mod 26 = 19 = T

m43 = (12 x 23 + 5 x 8 + 22 x    1) mod 26 = 276 +   40  +   22 = 338 mod 26 =   0 = A
m44 = (20 x 23 + 5 x 8 + 13 x    1) mod 26 = 460 +   40  +   13 = 513 mod 26 = 19 = T
m45 = (11 x 23 + 5 x 8 + 12 x    1) mod 26 = 253 +   40  +   12 = 305 mod 26 = 19 = T

m46 = (12 x 22 + 5 x 6 + 22 x    2) mod 26 = 264 +   30  +   44 = 338 mod 26 =   0 = A
m47 = (20 x 22 + 5 x 6 + 13 x    2) mod 26 = 440 +   30  +   26 = 496 mod 26 =   2 = C
m48 = (11 x 22 + 5 x 6 + 12 x    2) mod 26 = 242 +   30  +   24 = 296 mod 26 = 10 = K

Es decir, el texto en claro es:

THESOLUTIONTOTHISCHALLENGEISKNOWNPLAINTEXTATTACK

O, lo que es lo mismo:

THE SOLUTION TO THIS CHALLENGE IS KNOWN PLAINTEXT ATTACK

Por tanto, la solución a este reto es: KNOWN PLAINTEXT ATTACK.

2.- Solución mediante una herramienta especializada:

Utilizo la herramienta CrypTool (versión 1.4.40).

Tras introducir el criptograma realizo un ataque con texto claro conocido (introduciendo "THESOLUTI" como texto claro y "ZVMVCWQKX" como su correspondiente texto cifrado):
Como se observa en la figura anterior, la herramienta nos devuelve la matriz con la clave utilizada (tanto con los caracteres del alfabeto como con sus correspondientes valores numéricos).

Finalmente, desciframos el criptograma:
Por tanto, la solución a este reto es: KNOWN PLAINTEXT ATTACK.

3.- Solución creando un script de python:

from sympy import Matrix, pprint

alfabeto='ABCDEFGHIJKLMNOPQRSTUVWXYZ'

n=len(alfabeto)
print ''
print '[+] Modulo (n):', n

M = Matrix([
  [ 19,  7,  4 ],
  [ 18, 14, 11 ],
  [ 20, 19,  8 ]
])
print '[+] Matriz con texto claro conocido (M):'
pprint(M)

M_inv = M.inv_mod(26)
print "[+] Matriz inversa de M (M'):"
pprint(M_inv)

C = Matrix([
  [ 25, 21, 12 ],
  [ 21,  2, 22 ],
  [ 16, 10, 23 ]
])
print '[+] Matriz con criptograma correspondiente a texto claro conocido (C):'
pprint(C)

K = M_inv * C
for i in range(len(K)):
   K[i]=K[i]%n
print '[+] Matriz transpuesta de la utilizada como clave en el cifrado (Kt):'
pprint(K)

K = K.transpose()
print '[+] Matriz con la clave utilizada en el cifrado (K):'
pprint(K)

K_inv = K.inv_mod(26)
print "[+] Matriz inversa de K (K'):"
pprint(K_inv)

C = Matrix([
[25, 21, 12, 21, 2, 22, 16, 10, 23, 21, 20, 13, 15, 14, 7, 14, 10, 20,
 14, 9, 19, 18, 25, 11, 6, 8, 20, 11, 10, 2, 1, 16, 0, 11, 5, 16, 5, 6,
 1, 5, 8, 11, 23, 8, 1, 22, 6, 2]
])
print '[+] Criptograma (C):'
pprint(C)

texto_claro=''
i=0
while i  < len(C):
   Ci = Matrix([C[i],C[i+1],C[i+2]])
   Mi = K_inv * Ci
   for j in range(len(Mi)):
      texto_claro = texto_claro + alfabeto[Mi[j]%n:Mi[j]%n+1]
   i+=3
print '[+] Texto en claro (M):', texto_claro

Y tras ejecutar este script ya puedo ver que la solución a este reto es: KNOWN PLAINTEXT ATTACK.

******** PRÓXIMO RETO
Reto 28: Por publicar.

Reversing (II): Solución Reto HackYou "LoseYou"

$
0
0
Reconozco que soy muy malo resolviendo los retos CTF de tipo ingeniería inversa ('reversing'), pero me he propuesto aprender sobre ellos e iré compartiendo mis progresos, si es que los hay.

En este blog ya puse un post con la solución a un reto de 'reversing', titulado: Solución Reto Cybercamp "Oh my G0d!", en el que nos daban un fichero compilado de python (.pyc) y en el que para obtener la flag utilicé un decompilador de ese lenguaje de programación.

En el reto de este post, titulado "LoseYou",nos dan un fichero comprimido (rev200.zip) que contiene dos archivos (task2.bin y task2.exe).

Mi valoración sobre su dificultad es: 
. Ya he dicho que soy muy malo resolviendo este tipo de retos y no se trata de empezar con retos difíciles :).

Solución: Ejecuto el archivo task2.exe.
Se nos pide que introduzcamos un número (incluyo 0), y tras pulsar 'Enter' se nos muestra un mensaje de error en el que se nos indica el número correcto y se nos dice que probemos de nuevo.

Para resolver este reto utilizo OllyDbg, uno de los desensambladores y depuradores más populares, es decir, un programa que obtiene código ensamblador a partir de código máquina y, por tanto, una herramienta de ingeniería inversa ('reverse engineering') muy útil cuando no se dispone del código fuente, y que además es un depurador ('debugger') de código, lo que nos servirá para ir probando el código y poder modificarlo a nuestra conveniencia.

Abro task2.exe con OllyDbg y, en la parte superior izquierda de la ventana principal (donde se muestra el código en ensamblador), busco todas las cadenas de texto o 'strings' utilizadas en el programa (clic botón derecho, 'Search for'> 'All referenced strings'):
Y obtengo lo siguiente:
Como se observa en la figura anterior, vemos el mensaje de error que nos ha aparecido al ejecutar el programa: 'Fail... The number was...', y el mensaje que supuestamente aparecerá si introducimos el número correcto: 'You... you... win??? so lucky! Grab the flag:...'. Si hacemos doble clic sobre este último vamos a la parte del código donde se encuentra esta cadena de texto:
Dos líneas más arriba vemos la instrucción CMP de ensamblador. Esta instrucción compara dos operandos, en este caso el contenido de los registros EAX y ECX, y si son iguales se activa la flag Z, ya que la resta de ambos operandos sería cero y esta flag o bandera se activa (se pone a 1) cuando se ejecuta una instrucción y su resultado es cero.

Pongo un punto de interrupción ('breakpoint') en esa instrucción CMP (clic botón derecho sobre ella, 'Breakpoint' > 'Toggle') para que cuando ejecutemos el programa en modo depuración ('debug mode') éste se detenga en dicha instrucción:
Y ahora ejecuto el programa en modo depuración:
En ese momento:
Se nos pide que introduzcamos un número (incluyo 1), y tras pulsar 'Enter' el programa se detiene en el punto de interrupción que he puesto antes:
Justo debajo de la parte de la ventana principal donde se muestra el código en ensamblador se ve el contenido de los registros ECX y EAX que se comparan en la instrucción CMP.

Ahora, veo que ocurriría si ejecuto la siguiente línea de código:
La instrucción en esta línea es JNE (equivalente a JNZ), es decir, un salto condicional a la dirección indicada (en este caso 0040139C) si el resultado de la instrucción anterior es diferente de cero (en nuestro caso si ECX y EAX son diferentes) o, lo que es lo mismo, si la flag Z está desactivada (es igual a 0). En nuestro caso, como ECX y EAX son distintos, se produce el salto a la dirección establecida:
Como se ve en la figura anterior, tras el salto a la dirección indicada se mostraría el mensaje de error.

Para evitar esto, es decir, evitar que salte y conseguir que el flujo del programa se dirija al mensaje que aparecería si introdujeramos el número correcto, podemos modificar la instrucción JNE por su contraria (JE o JZ, salto condicional si el resultado de la instrucción anterior es cero - en nuestro caso si EXC y EAX son iguales - o, lo que es lo mismo, si la flag Z está activada - es igual a 1), de la siguiente manera:
Como se ve, ahora, como ECX y EAX son distintos, no se produce el salto a la dirección indicada y el programa continuará para mostrar el mensaje con la flag del reto:
Por tanto, la solución a este reto es: oh_you_cheat3r.

Reversing (III): Solución Reto 3 Atenea Escuela

$
0
0
En este post explico la solución a uno de los retos de Atenea Escuela, el reto 3 de la categoría de 'reversing' referido al "Análisis dinámico de aplicaciones".

Atenea Escuela es una plataforma básica de desafíos de seguridad informática compuesta por diferentes retos para fomentar el aprendizaje de los usuarios menos entendidos en el campo de la seguridad.

Las soluciones se pueden ver en la propia plataforma y creo que es una muy buena forma de aprender sobre esta temática.

Mi valoración sobre la dificultad de este reto es: . En él nos dan un archivo ejecutable (reto3.exe) y nos piden contestar a dos preguntas:

1.- ¿Cuál es el valor correcto que hay que introducir para conseguir mostrar el mensaje de éxito?.

2.- ¿Cuánto vale el registro EAX y las banderas de acarreo, de cero y de signo?.

Solución: Ejecuto el archivo reto3.exe y se me pide que introduzca un número:
Introduzco un número cualquiera, por ejemplo: 1234, y se muestra un mensaje de error:
Como en el reto anterior sobre 'reversing' que puse en este blog, utilizo OllyDbg y procedo de la misma forma que en el citado reto, aunque ya adelanto que en esta ocasión no será suficiente con hacerlo de esa manera.

Abro en esa herramienta el archivo ejecutable (reto3.exe) y, en la parte superior izquierda de la ventana principal (donde se muestra el código en ensamblador), busco todas las cadenas de texto o'strings' utilizadas en el programa (clic botón derecho, 'Search for' > 'All referenced strings'):
obtengo lo siguiente:
Como se observa en la figura anterior, entre las cadenas de texto está el mensaje de error que me ha aparecido al ejecutar el programa: "Valor incorrecto! :("y también el mensaje que supuestamente aparecerá si introduzco el número correcto"Valor correcto!". Si hago doble clic sobre este último voy a la parte del código donde se encuentra esta cadena de texto:
Dos líneas más arriba veo la instrucción TEST de ensamblador. Esta instrucción es en cierto sentido igual a CMP, pero no realiza una resta sino una operación AND entre dos operandos, en nuestro caso entre el contenido del registro EAX y el contenido del mismo registro EAX, y sirve para comprobar si ambos operandos son iguales a cero, ya que en ese caso la operación lógica AND entre ellos será cero, en nuestro caso es lo mismo que decir que el contenido de EAX sea cero. Si ambos operandos son cero, se activa la flag Z, ya que esta flag o bandera se activa (se pone a 1) cuando se ejecuta una instrucción y su resultado es cero.

Pongo un punto de interrupción ('breakpoint') en esa instrucción TEST (clic botón derecho sobre ella, 'Breakpoint' > 'Toggle') para que cuando ejecute el programa en modo depuración ('debug mode') éste se detenga en dicha instrucción:
Y ahora ejecuto el programa en modo depuración:
En ese momento:
Se me pide que introduzca un número (incluyo 1234) y tras pulsar 'Enter' el programa se detiene en el punto de interrupción que he puesto antes:
Justo debajo de la parte de la ventana principal donde se muestra el código en ensamblador se ve el contenido del registro EAX, que es igual a cero.

Ahora, veo que ocurriría si ejecuto la siguiente línea de código:

La instrucción en esta línea es JZ (equivalente a JE), es decir, un salto condicional a la dirección indicada (en este caso 0x004014AF) si el resultado de la instrucción anterior es igual a cero (en nuestro caso si EAX es igual a cero) o, lo que es lo mismo, si la flag Z está activada (es igual a 1). En nuestro caso, como EAX es igual a cero, se produciría el salto a la dirección establecida. Como se ve en la figura anterior, tras el salto a la dirección indicada se mostraría el mensaje de error.

Al igual que en el reto anterior, para evitar que salte y conseguir que el flujo del programa se dirija al mensaje que aparecería si introdujera el número correcto, puedo modificar la instrucción JZ por su contraria (JNZ o JNE, salto condicional si el resultado de la instrucción anterior es diferente de cero - en nuestro caso si EAX es diferente de cero - o, lo que es lo mismo, si la flag Z no está activada - es igual a 0), de la siguiente manera:
Como se ve, ahora, como el contenido de EAX es igual a cero, no se producirá el salto a la dirección indicada y el programa continuará para mostrar el mensaje de éxito, aquel que aparecería si hubiera introducido el valor correcto:
Sin embargo no puedo dar una respuesta a la primera pregunta del reto, es decir, no conozco qué valor hay que introducir para que se muestre este mensaje, por lo que me temo que hay que analizar el código con mayor profundidad.

Reinicio la ejecución del programa en modo depuración ('Debug''Restart'):
Y, de la misma forma que anteriormente, vuelvo a posicionarme en el sitio del código donde aparece el mensaje que supuestamente se mostrará si introdujera el número correcto"Valor correcto!".
Coloco un punto de interrupción ('breakpoint') en  la dirección 0x401495. En la dirección anterior a esa se moverá el número que introduzca al registro EAX.
Ejecuto el programa en modo depuración, introduzco el número que se nos solicita (incluyo otra vez 1234) y tras pulsar 'Enter' el programa se detiene en el primer punto de interrupción, el que acabo de poner justo antes.

Efectivamente, tanto debajo de la parte de la ventana principal donde se muestra el código en ensamblador como en la parte superior derecha de dicha ventana principal, veo que el contenido del registro EAX es 0x4D2 en hexadecimal (1234 en decimal):
Ejecuto la siguiente instrucción y veo que la siguiente es una CALL a la dirección 0x401440.
Ejecuto la CALL, pero no con el botón que se muestra en la figura anterior, 'Step over (F8)', sino con el que se muestra en la figura siguiente, 'Step into (F7)'. La diferencia entre ambos es que mientras el primero ejecutaría todo el código de la CALL sin interrupción y continuaría en la siguiente línea después de ella, el segundo entrará en el código de la CALL y permitirá ejecutar sus instrucciones una a una.
En el código de la CALL, hasta la instrucción RETN de la dirección 0x401462, que es la última y devuelve el control a la instrucción siguiente a la CALL, veo que hay una instrucción JLE que provocará un salto condicional a la dirección indicada si el número que he introducido, 1234 en decimal (4D2 en hexadecimal), es menor o igual que 256 en decimal (100 en hexadecimal).
En nuestro caso, como el número que he introducido, 1234 en decimal, es mayor que 256 en decimal no se producirá el salto, y continuando con la ejecución veo que la instrucción JG que se encuentra después provocará un salto condicional a la dirección indicada si el número que he introducido, 1234 en decimal (4D2 en hexadecimal), es mayor que 666 en decimal (29A en hexadecimal).
En resumen: si el número introducido es menor o igual que 256 o dicho número es mayor que 666 se producirá un salto a la dirección 0x0040145C en la que se moverá 0 al registro EAX y, tras salir de la CALL, el programa mostrará el mensaje de error. Como nosotros hemos introducido 1234 en decimal, que es mayor que 666 en decimal, se visualizará dicho mensaje:
Por el contrario, si el número introducido es mayor que 256 y menor o igual que 666 se moverá 1 al registro EAX y, tras salir de la CALL, el programa mostrará el mensaje de éxito.
Por tanto la respuesta a la primera pregunta es que el número introducido tiene que estar comprendido entre 257 y 666, ambos inclusive, para que se muestre el mensaje de éxito, mientras que para contestar a la segunda pregunta nos fijamos en la parte superior derecha de la ventana principal de OllyDbg:
Es decir, la respuesta a la segunda pregunta es que el registro EAX tiene valor 1, la bandera de acarreo (C) tiene valor 1, la de cero (Z) tiene valor 0 y la de signo (S) tiene valor 1.

Reversing (IV): Solución Reto Challenge Land "Reversing 3"

$
0
0
En este post la solución a uno de los retos de 'reversing' de la plataforma Challenge Land.

Se trata del reto "Reversing 3" y mi valoración sobre su dificultad es: ☆☆☆.

Su enunciado dice lo siguiente:


Get string of serial and do rot13.

Y nos dan un archivo ejecutable (level3.exe).

Solución: Ejecuto este fichero se me pide que introduzca un número de serie:
Introduzco una cadena cualquiera de caracteres y dígitos, por ejemplo: abc123, y se muestra un mensaje de error:
Al igual que en los dos retos anteriores sobre 'reversing' que he puesto en este blog (II y III), utilizo OllyDbg y abro en esta herramienta el archivo ejecutable (level3.exe).

Como en los dos retos anteriores a los que he hecho referencia, empiezo el análisis buscando todas las cadenas de texto o'strings' utilizadas en el programa (en la parte superior izquierda de la ventana principal: clic botón derecho, 'Search for' > 'All referenced strings'):
Y obtengo lo siguiente:
Como se observa en la figura anterior, entre las cadenas de texto está el mensaje de error que me ha aparecido al ejecutar el programa: "Wrong."y también el mensaje que supuestamente aparecerá si introduzco el número de serie correcto"Well done.". Si hago doble clic sobre este último voy a la parte del código donde se encuentra esta cadena de texto:
Tras el análisis dinámico del programa, veo que es en el código a partir de la dirección 0x00401290 donde se realiza ROT13 de la cadena que he introducido como número de serie.

Para explicarlo pongo un punto de ruptura en la instrucción CALL que llama a dicho código (clic botón derecho sobre ella, 'Breakpoint' > 'Toggle') para que cuando ejecute el programa en modo depuración ('debug mode') éste se detenga en dicha instrucción:
Tras ejecutar el programa en modo depuración, 'Run debugged application (F9)'introduzco otra vez abc123 como número de serie, pulso 'Enter' el programa se detiene en el punto de interrupción que acabo de colocar.

Veo que el registro EAX contiene la dirección donde se ha incluido la cadena introducida (abc123) y me llama la atención que EBX contiene una dirección en la que se almacena otra cadena (AvrQQsXjDk25Jrh):
Ejecuto la CALL, 'Step into (F7)', entrando en su código para ejecutar instrucción a instrucción:
Básicamente, lo que hace esta rutina es sumar 13 (D en hexadecimal) a cada uno de los caracteres de la cadena introducida como número de serie:
Como se ve en la figura anterior, tras finalizar la ejecución de la rutina, en la dirección que contiene el registro EAX se encuentra la cadena resultado (nop>?@) de sumar 13 (D en hexadecimal) a cada uno de los caracteres de la cadena que se ha introducido como número de serie (abc123) y EBX sigue conteniendo la dirección en la que se almacena la otra cadena que me ha llamado la atención (AvrQQsXjDk25Jrh).

Es decir:

- Para el primer carácter introducido como número de serie ('a'):

Código ASCII de 'a': en decimal 97, en hexadecimal 61
ROT13: 97 + 13 =  110 (en decimal); 61 + D = 6E (en hexadecimal)

El código ASCII 110 en decimal (6E en hexadecimal) se corresponde con 'n'.

- ...

- Para el último carácter introducido como número de serie ('3'):

Código ASCII de '3': en decimal 51, en hexadecimal 33
ROT13: 51 + 13 =  64 (en decimal); 33 + D = 40 (en hexadecimal)

El código ASCII 64 en decimal (40 en hexadecimal) se corresponde con '@'.

Por tanto, el resultado de hacer ROT13 sobre la cadena que he introducido como número de serie (abc123) es nop>?@

Posteriormente, cuando se devuelve el control a la instrucción siguiente a la CALL, veo las dos cadenas que el programa comparará carácter a carácter (AvrQQsXjDk25Jrh y nop>?@) con objeto de determinar si se muestra el mensaje de éxito o de error, en función de si los caracteres de ambas coinciden o no, respectivamente.
Lógicamente, como he introducido una cadena cualquiera (abc123) se mostrará el mensaje de error:
Una vez completado el análisis dinámico del programa, ya estoy en disposición de calcular el número de serie correcto y superar el reto. Para ello, basta con restar 13 (D en hexadecimal) a cada uno de los caracteres de la cadena AvrQQsXjDk25Jrh e introducir la cadena resultado como número de serie.

Aunque se puede calcular perfectamente a mano, creo un pequeño script en python:

string = 'AvrQQsXjDk25Jrh'
solution = ''
for x in string:
     solution = solution + (chr(ord(x)-13))

print 'solution:', solution

Lo ejecuto y veo el número de serie correcto:
Para comprobar si lo he hecho bien, ejecuto otra vez el programa e introduzco ese número de serie:
Por tanto, la solución a este resto es: 4ieDDfK]7^%(=e[


Reversing (V): Solución Reto Challenge Land "Reversing 6"

$
0
0
En este post la solución a otro de los retos de 'reversing' de la plataforma Challenge Land.

Se trata del reto "Reversing 6" y mi valoración sobre su dificultad es: ☆☆☆☆.

Su enunciado dice lo siguiente:


Within the application resources is a BMP with the answer.

Y nos dan un archivo ejecutable (level6.exe).

Solución: Antes que nada decir que creo que se trata de un reto muy fácil, el enunciado nos dice claramente cómo resolverlo, e incluso que tiene muy poco que ver con la ingeniería inversa.

Para resolverlo utilizo el software 'ResourcesExtract', una pequeña utilidad que nos permite extraer recursos empaquetados (iconos, bitmaps, etc.) en archivos .exe, .dll, ...

Ejecuto esta herramienta e indico la ruta del archivo a explorar (level6.exe) y la carpeta destino de los recursos a extraer:
Tras pulsar 'Start' veo que en la carpeta destino se extrae un archivo de imagen (level6_1.bmp). Abro este archivo y ya puedo ver la solución de este reto:
Y para superar este reto sólo me falta introducir esa cadena como solución:

Reversing (VI): Solución Reto Challenge Land "Reversing 4"

$
0
0
En este post la solución a otro de los retos de 'reversing' de la plataforma Challenge Land.

Se trata del reto "Reversing 4" y mi valoración sobre su dificultad es: ☆☆☆☆.

Su enunciado dice lo siguiente:


Get md5 string and find the word that generates it.

Y nos dan un archivo ejecutable (level4.exe).

Solución: Ejecuto este fichero se me pide que introduzca un número de serie:
Introduzco una cadena cualquiera de caracteres y dígitos, por ejemplo: abc123, y se muestra un mensaje de error:
Como en retos anteriores de 'reversing' que he puesto en este blog, utilizo OllyDbg y empiezo el análisis buscando todas las cadenas de texto o'strings' utilizadas en el programa (en la parte superior izquierda de la ventana principal: clic botón derecho, 'Search for' > 'All referenced strings'):
Y obtengo lo siguiente:
Como se observa en la figura anterior, entre las cadenas de texto está el mensaje de error que me ha aparecido al ejecutar el programa: "Wrong."y también el mensaje que supuestamente aparecerá si introduzco el número de serie correcto"Well done.". Si hago doble clic sobre este último voy a la parte del código donde se encuentra esta cadena de texto:
Si no entiendo mal el enunciado, el programa obtiene el hash md5 del número de serie que se introduce y éste se compara con el hash md5 del número de serie correcto. Por tanto, una de las cadenas que se pasan como parámetros a la función strcmp que se llama en la dirección 0x004013BA debe corresponderse con el hash md5 del número de serie correcto y la otra con el hash del número de serie introducido.

Pongo un punto de ruptura en la instrucción CALL que llama a dicha función (clic botón derecho sobre ella, 'Breakpoint' > 'Toggle') para que cuando ejecute el programa en modo depuración ('debug mode') éste se detenga en dicha instrucción:
Tras ejecutar el programa en modo depuración, 'Run debugged application (F9)'introduzco otra vez abc123 como número de serie, pulso 'Enter' el programa se detiene en el punto de interrupción que acabo de colocar.

Veo que el registro EAX contiene la dirección a partir de la cual se almacena un hash md5 y  el registro EDX contiene otra dirección a partir de la cual se almacena otro hash md5
, y que ambas entran como parámetros a la función strcmp.
Para comprobar si estoy en lo cierto y uno de esos hash md5 se corresponde con el número de serie correcto y el otro con el número de serie introducido, utilizo una de las muchas herramientas online existentes para obtener el texto en claro correspondiente a un hash (siempre y cuando haya suerte, ya que una función hash es de un solo sentido y, por tanto, teóricamente no es reversible):
Como se observa en la figura anterior el primer hash se corresponde con la cadena "orange", mientras que el texto en claro a partir del cual se ha generado el segundo es "abc123" (la cadena que he introducido como número de serie).

Por tanto, si no estoy equivocado el número de serie correcto es "orange". Lo compruebo:
para superar este reto sólo me falta introducir esa cadena como solución:

Reversing (VII): Solución Reto Challenge Land "Reversing 5"

$
0
0
En este post la solución a otro de los retos de 'reversing' de la plataforma Challenge Land.

Se trata del reto "Reversing 5" y mi valoración sobre su dificultad es: ☆☆☆☆.

Su enunciado dice lo siguiente:


With a hex editor, the string is attached to the program, you can be found with the command strings.

Y nos dan un archivo ejecutable (level5.exe).

Solución: Antes que nada, como en el post V sobre 'reversing' que puse en este blog, decir que se trata de un reto muy fácil, el enunciado nos dice claramente cómo resolverlo, e incluso que creo que tiene muy poco o nada que ver con la ingeniería inversa.


Para resolverlo abro el archivo ejecutable (level5.exe) con un editor de texto:
Al final del archivo puedo ver la solución: "AttachedString".

para superar este reto sólo me falta introducir esa cadena como solución:

Reversing (VIII): Solución Reto Challenge Land "Reversing 2"

$
0
0
En este post la solución a otro de los retos de 'reversing' de la plataforma Challenge Land.

Se trata del reto "Reversing 2" y mi valoración sobre su dificultad es: ☆☆☆☆.

Su enunciado dice lo siguiente:


After program execution for answers.

Y nos dan un archivo ejecutable (level2.exe).

Solución: Ejecuto este fichero se me pide que introduzca un número de serie:
Introduzco una cadena cualquiera de caracteres y dígitos, por ejemplo: abc123, y se muestra un mensaje de error:
Como en retos anteriores de 'reversing' que he puesto en este blog, utilizo OllyDbg y empiezo el análisis buscando todas las cadenas de texto o 'strings' utilizadas en el programa (en la parte superior izquierda de la ventana principal: clic botón derecho, 'Search for' > 'All referenced strings'):
obtengo lo siguiente:
En esta ocasión sólo veo la cadena de texto correspondiente al mensaje de error que me ha aparecido al ejecutar el programa: "Wrong.", pero no la que supuestamente debería aparecer si introdujera el número de serie correcto. Si hago doble clic sobre la cadena del mensaje de error voy a la parte del código donde se encuentra ese texto:
Pongo un punto de interrupción en la instrucción CALL que llama a la función strcmp que se encuentra dos líneas más arriba (clic botón derecho sobre ella, 'Breakpoint' > 'Toggle') para que cuando ejecute el programa en modo depuración ('debug mode') éste se detenga en dicha instrucción y pueda ver que dos cadenas compara:
Tras ejecutar el programa en modo depuración, 'Run debugged application (F9)'introduzco otra vez abc123 como número de serie, pulso 'Enter' el programa se detiene en el punto de interrupción que acabo de colocar.

Veo que el registro EAX contiene la dirección a partir de la cual se almacena una cadena de caracteres ("6LPw3vDYja9KrT2V") y el registro EDX contiene otra dirección a partir de la cual se almacena la cadena de caracteres que he introducido como número de serie ("abc123")
, y que ambas entran como parámetros a la función strcmp.

Sin embargo, no veo posteriormente un salto condicional en función de la comparación realizada de esas dos cadenas y creo que el programa, se introduzca el número de serie que se introduzca, muestra siempre el mensaje de error.

Por tanto, pruebo introducir la cadena "6LPw3vDYja9KrT2V" como solución:

Reversing (IX): Solución Reto Challenge Land "Reversing 1"

$
0
0
En este post la solución a otro de los retos de 'reversing' de la plataforma Challenge Land.

Se trata del reto "Reversing 1" y mi valoración sobre su dificultad es: ☆☆☆☆.

Su enunciado dice lo siguiente:


Get the correct serial stack.

Y nos dan un archivo ejecutable (level1.exe).

Solución: Ejecuto este fichero se me pide que introduzca un número de serie:
Introduzco una cadena cualquiera de caracteres y dígitos, por ejemplo: abc123, y se muestra un mensaje de error:
Como en retos anteriores de 'reversing' que he puesto en este blog, utilizo OllyDbg y empiezo el análisis buscando todas las cadenas de texto o 'strings' utilizadas en el programa (en la parte superior izquierda de la ventana principal: clic botón derecho, 'Search for' > 'All referenced strings'):
obtengo lo siguiente:
Como se observa en la figura anterior, entre las cadenas de texto está el mensaje de error que me ha aparecido al ejecutar el programa: "Wrong."y también el mensaje que supuestamente aparecerá si introduzco el número de serie correcto"Well done.". Si hago doble clic sobre este último voy a la parte del código donde se encuentra esta cadena de texto:
Pongo un punto de interrupción en la instrucción CALL que llama a la función strcmp que se encuentra cuatro líneas más arriba (clic botón derecho sobre ella, 'Breakpoint' > 'Toggle') para que cuando ejecute el programa en modo depuración ('debug mode') éste se detenga en dicha instrucción y pueda ver que dos cadenas compara:
Tras ejecutar el programa en modo depuración, 'Run debugged application (F9)'introduzco otra vez abc123 como número de serie, pulso 'Enter' el programa se detiene en el punto de interrupción que acabo de colocar.

Veo que el registro EAX contiene la dirección a partir de la cual se almacena una cadena de caracteres ("Kcgcv8LsmV3nizfJ") y el registro EDX contiene otra dirección a partir de la cual se almacena la cadena de caracteres que he introducido como número de serie ("abc123")
, y que ambas entran como parámetros a la función strcmp.

Por tanto, la solución a este resto es: Kcgcv8LsmV3nizfJ

Reversing(X): Solución Reto RingZer0 Team "Windows x86 Reversing is cool!"

$
0
0
En este post la solución a uno de los retos de 'reversing' de la plataforma RingZer0 Team.

Este reto tiene el título "Windows x86 Reversing is cool!" y mi valoración sobre su dificultad es:  ☆☆.

Su enunciado dice lo siguiente:

The key of this challenge is to understood what`s going on.

Y nos dan un archivo ejecutable (1231fa8523f32a9118993946bccfb9ec203.exe).

Solución: Ejecuto este fichero se me pide que introduzca una clave:
Introduzco una cadena cualquiera de caracteres y dígitos, por ejemplo: "abc123", y se muestra un mensaje de error:
Utilizo OllyDbg para realizar un análisis dinámico de la aplicación. Abro en esta herramienta el archivo ejecutable y empiezo el análisis buscando todas las cadenas de texto o 'strings' utilizadas en el programa (en la parte superior izquierda de la ventana principal: clic botón derecho, 'Search for' > 'All referenced strings'):
Y obtengo lo siguiente:
Como se observa en la figura anterior, entre las cadenas de texto está el mensaje de error que me ha aparecido al ejecutar el programa: "Wrong Key!". Si hago doble clic sobre este mensaje voy a la parte del código donde se encuentra esta cadena de texto:
Dos líneas más arriba de la instrucción en 0x0040140B, veo una instrucción que comparará la longitud de la cadena que introduzca como clave con 6, y la instrucción que está justo debajo de ésta (JE) no saltará a la instrucción indicada si dicha longitud es diferente de 6 y, en ese caso, se mostrará el mensaje de error. Es decir, la longitud de la clave correcta es 6.

Para ver lo que he dicho en el párrafo anterior pongo un punto de interrupción en la instrucción de salto condicional (JE) en 0x00401409. Reinicio la ejecución en modo depuración e introduzco nuevamente la cadena "abc123" como clave: 
En las figuras anteriores veo que el registro ECX contiene la dirección a partir de la cual se almacena la cadena de caracteres que he introducido como clave y el registro EAX contiene la longitud de ésta, y, en consecuencia, como el valor de este último registro es igual a 6, en la instrucción en la que he puesto el punto de interrupción se producirá el salto a la dirección 0x0040146F.

A continuación el programa se introduce en un bucle que ejecuta 6 veces (longitud de la cadena incluida como clave y también de la correcta) y realiza una operación XOR entre cada uno de los caracteres de la cadena incluida como clave y el valor D3 en hexadecimal (211 en decimal).

Para ver esto último pongo un punto de interrupción en la instrucción de salto condicional (JB) en 0x00401483 y continúo con la ejecución en modo depuración hasta ese 'breakpoint':
En las figuras anteriores se ve que el registro EBX contiene el valor 0 y el registro EAX contiene la longitud de la cadena que he introducido como clave, y, en consecuencia, como el valor contenido en EBX es menor que 6, en la instrucción en la que he puesto el punto de interrupción se producirá el salto a la dirección 0x00401423.

A partir de la dirección a la que se produce el salto el programa realizará una operación XOR entre el primer carácter de la cadena incluida como clave (el código ASCII en hexadecimal de "a" es 61, en decimal 97) y el valor D3 en hexadecimal (211 en decimal).

Posteriormente el programa comparará el resultado de esa operación XOR con un valor determinado y si ambos son distintos se mostrará el mensaje de error, mientras que si ambos son iguales se continuará con el segundo carácter de la clave introducida, y así sucesivamente.

Para verlo pongo dos puntos de interrupción, el primero en la instrucción que realiza la operación XOR y el segundo en la instrucción de salto condicional (JE) en función de si el resultado de dicha operación es igual a un valor determinado o no.
Como se ve en las figuras anteriores:

1.- En la instrucción XOR en 0x00401430 se almacena en DX el resultado de la operación:

00000061 XOR FFFFFFD3 = FFFFFFB2

2.- En la instrucción CMP en 0x0040144E se compara el valor de AL (97 en hexadecimal) con el valor de DL (B2 en hexadecimal).

3.- Como AL y DL son diferentes no se produce el salto en la instrucción JE en 0x00401450 y se mostrará el mensaje de error.

Por tanto, me queda claro que el primer carácter de la clave tiene que ser el resultado de:

97 XOR D3 = 44  

El carácter correspondiente al código ASCII 44 en hexadecimal (68 en decimal) es "D".

Como he introducido como clave una cadena cualquiera ("abc123") y, como era más que probable, el resultado de la operación XOR del primer carácter ("a", 61 en hexadecimal) con D3 no es igual que el esperado por el programa (97 en hexadecimal), tal y como ya he dicho, el programa finalizará mostrando el mensaje de error. Para evitar esto último y poder calcular los 5 caracteres restantes de la clave correcta hago lo siguiente:

1.- Tras la primera operación XOR modifico DL poniendo 97 en lugar de B2:
Con lo que el programa realizará la operación XOR entre el segundo carácter de la clave introducida ("b", en hexadecimal 62) y D3, almacenándose el resultado en DX:


00000062 XOR FFFFFFD3 = FFFFFFB1

Después se comparará AL (E0) con DL (B1), por lo que el segundo carácter de la clave tiene que ser el resultado de:

E0 XOR D3 = 33  

El carácter correspondiente al código ASCII 33 en hexadecimal (51 en decimal) es "3".

2.- Tras la segunda operación XOR modifico DL poniendo E0 en lugar de B1:
Con lo que el programa realizará la operación XOR entre el tercer carácter de la clave introducida ("c", en hexadecimal 63) y D3, almacenándose el resultado en DX:


00000063 XOR FFFFFFD3 = FFFFFFB0

Después se comparará AL (EB) con DL (B0), por lo que el tercer carácter de la clave tiene que ser el resultado de:

EB XOR D3 = 38  

El carácter correspondiente al código ASCII 38 en hexadecimal (56 en decimal) es "8".

3.- Tras la tercera operación XOR modifico DL poniendo EB en lugar de B0:
Con lo que el programa realizará la operación XOR entre el cuarto carácter de la clave introducida ("1", en hexadecimal 31) y D3, almacenándose el resultado en DX:


00000031 XOR FFFFFFD3 = FFFFFFE2

Después se comparará AL (A0) con DL (E2), por lo que el cuarto carácter de la clave tiene que ser el resultado de:

A0 XOR D3 = 73  

El carácter correspondiente al código ASCII 73 en hexadecimal (115 en decimal) es "s".

4.- Tras la cuarta operación XOR modifico DL poniendo A0 en lugar de E2:
Con lo que el programa realizará la operación XOR entre el quinto carácter de la clave introducida ("2", en hexadecimal 32) y D3, almacenándose el resultado en DX:


00000032 XOR FFFFFFD3 = FFFFFFE1

Después se comparará AL (B8) con DL (E1), por lo que el quinto carácter de la clave tiene que ser el resultado de:

B8 XOR D3 = 6B  

El carácter correspondiente al código ASCII 6B en hexadecimal (107 en decimal) es "k".

5.- Finalmente, tras tras la quinta operación XOR modifico DL poniendo B8 en lugar de E1:
Con lo que el programa realizará la operación XOR entre el sexto carácter de la clave introducida ("3", en hexadecimal 33) y D3, almacenándose el resultado en DX:


00000033 XOR FFFFFFD3 = FFFFFFE0

Después se comparará AL (E1) con DL (E0), por lo que el sexto carácter de la clave tiene que ser el resultado de:

E1 XOR D3 = 32  

El carácter correspondiente al código ASCII 32 en hexadecimal (50 en decimal) es "2".

Por tanto, la clave correcta es "D38sk2". Ejecuto el programa e introduzco esta clave:
Y para superar este reto lo único que me falta por hacer es introducir la flag en la plataforma:

Reversing(XI): Solución Reto Yoire "01_crackme_very_easy"

$
0
0
En este post la solución a uno de los retos de 'reversing' de la plataforma Yoire.

Este reto tiene el título "01_crackme_very_easy" y mi valoración sobre su dificultad es:  ☆☆.

Su enunciado dice lo siguiente:

Descarga y analiza el ejecutable (PE) suministrado, e indica la solución en el recuadro de texto mostrado más abajo.

Y nos dan un archivo ejecutable (01_crackme_very_easy.exe).

Solución: Ejecuto este fichero y se me pide que introduzca un número de serieIncluyo una cadena cualquiera de caracteres y dígitos, por ejemplo: "abc123", y, tras pulsar 'Check serial', se muestra un mensaje de error:
Utilizo OllyDbg para realizar un análisis dinámico de la aplicación. Abro en esta herramienta el archivo ejecutable y empiezo el análisis buscando todas las cadenas de texto o 'strings' utilizadas en el programa (en la parte superior izquierda de la ventana principal: clic botón derecho, 'Search for' > 'All referenced strings'):
Y obtengo lo siguiente:
Entre ellas hay una cadena muy sospechosa :) "AreYouLookingAtMe?", que posiblemente sea incluso el número de serie que estoy buscando, ya que este reto está catalogado en la propia plataforma como muy fácil. Si hago doble clic sobre este mensaje voy a la parte del código donde se encuentra esa cadena de texto.

Pongo un punto de interrupción en la instrucción en 0x0040113C y otro en la que se encuentra en 0x0040115A. En esta última, en función del valor contenido en el registro EAX, se bifurcará para que el programa muestre el mensaje de error o, por el contrario, se continuará para mostrar el mensaje de éxito.

Ejecuto la aplicación en modo depuración y vuelvo a introducir "abc123" como número de serie. Tras pulsar el botón 'Check serial', el programa se detiene en el primer punto de interrupción que he puesto:  
El registro ECX contiene la dirección a partir de la que se almacena el número de serie que he introducido ("abc123") y EAX contiene la longitud del mismo.

Además, tras la ejecución de la instrucción en la que he puesto el punto de interrupción, EDX contendrá la dirección a partir de la que se almacenará la cadena "AreYouLookingAtMe?":
Un poco más adelante veo como se moverá el primer carácter del número de serie que he introducido ("a") a AL y en la instrucción siguiente que éste se comparará con el primer carácter de la cadena sospechosa ("AreYouLookingAtMe?").
Si ambos caracteres son diferentes en la siguiente instrucción (JNE) se producirá el salto a la instrucción indicada y se continuará hasta mostrar el mensaje de error. En el caso de que sean iguales se repetirá el proceso con el segundo carácter del número de serie introducido y de la cadena sospechosa, y así sucesivamente. Finalmente, si el número de serie introducido coincide con "AreYouLookingAtMe?" se mostrará el mensaje de éxito.

Para ver si estoy en lo cierto ejecuto el programa e introduzco "AreYouLookingAtMe?" como número de serie:
Pulso 'Check serial'y se muestra el mensaje de éxito:
Y lo único que me falta para superar este reto es introducir el número de serie correcto en la plataforma:


Reversing(XII): Solución Reto Yoire "04_crackme_hard"

$
0
0
En este post la solución a otro de los retos de 'reversing' de la plataforma Yoire.

Este reto tiene el título "04_crackme_hard" y mi valoración sobre su dificultad es:  ☆☆.

Su enunciado dice lo siguiente:

Descarga y analiza el ejecutable (PE) suministrado, e indica la solución en el recuadro de texto mostrado más abajo.

Y nos dan un archivo ejecutable (04_crackme_hard.exe).

Solución: Abro el archivo en OllyDbg. Lo ejecuto en modo depuración y aparece el siguiente mensaje:
El programa detecta que se está ejecutando desde un depurador ('debugger') y muestra esta ventana con objeto de evitar el análisis.

Intento averiguar qué técnica 'anti-debugging' está utilizando para ver si puedo evitar esa protección. Para ello, reinicio la ejecución en modo depuración y busco todas las llamadas con destino fuera del módulo actual realizadas en el programa (en la parte superior izquierda de la ventana principal: clic botón derecho, 'Search for' > 'All intermodular calls'):
Y obtengo lo siguiente:
En la figura anterior se ve la llamada a la función IsDebuggerPresent, una función de la librería Kernel32.dll que devuelve un valor distinto de 0 si el programa está siendo 'debuggeado' y 0 en caso contrario.

Se trata de una técnica 'anti-debugging' muy fácil de implementar, ya que basta con realizar la llamada a la función y comprobar el valor que devuelve, pero también es muy fácil de evitar.

Hago doble clic sobre esa llamada y voy a la parte del código donde se encuentra:
Como se observa en la figura anterior, justo después de la llamada a esta función, se comprueba el valor del registro EAX y si éste es cero (el programa no está siendo 'debuggeado') se produce un salto a la dirección 0x40102D

Pongo un punto de interrupción en la instrucción de salto condicional (JZ) y ejecuto el programa en modo depuración:
Como se observa en las figuras anteriores, el valor de EAX es diferente de cero (el programa está siendo 'debuggeado') y, por tanto, no se producirá el salto y el programa continuará para mostrar la ventana en la que se indica que se aborta la ejecución.

Para saltarnos esta protección simplemente cambiamos la instrucción JZ por su contraria (JNZ):
Y como se ve en la figura anterior, ahora el salto se produce y el programa se comportará como si no estuviera siendo 'debuggeado', y se me pide que introduzca un número de serie. Incluyo una cadena cualquiera de caracteres y dígitos, por ejemplo: "abc123", y, tras pulsar 'Check serial', se muestra un mensaje de error:
Reinicio la ejecución en modo depuración y, como siempre suelo hacer cuando utilizo OllyDbg, empiezo el análisis buscando todas las cadenas de texto o 'strings' utilizadas en el programa (en la parte superior izquierda de la ventana principal: clic botón derecho, 'Search for' > 'All referenced strings'):
Y obtengo lo siguiente:
Como se observa en la figura anterior, entre las cadenas de texto está el mensaje de error que me ha aparecido al ejecutar el programa: "Sorry, the serial is invalid :("y también el mensaje que supuestamente aparecerá si introduzco el número de serie correcto"Your serial is valid :)". Si hago doble clic sobre este último voy a la parte del código donde se encuentra esta cadena de texto:
Como se ve en la figura anterior, hay 11 comparaciones de caracteres predeterminados con el contenido de ciertos Bytes de memoria en los que se incluyen los caracteres que se introducen como número de serie. Por tanto, el número de serie correcto tiene 11 caracteres de longitud.

Tras cada una de las comparaciones, si los dos caracteres son iguales se continuará con la siguiente comparación, mientras que si son distintos se produce un salto a la dirección 0x00401204 y el programa mostrará el mensaje de error. Si los caracteres en todas las comparaciones son iguales se mostrará el mensaje de éxito.

Para verlo exactamente pongo un punto de interrupción en la primera comparación de caracteres. Ejecuto el programa en modo depuración, me salto la protección 'anti-debugging' como ya he explicado anteriormente, introduzco como número de serie "123456789ab" y el programa se parará en el punto de interrupción que he puesto en la primera comparación. Voy ejecutando una detrás de otra (tras cada una de ellas si los dos caracteres comparados son distintos habrá que cambiar la instrucción JNE que le sigue por su contraria - JE -, para que se continúe con la siguiente comparación) y veo lo siguiente (el primer carácter que se indica es fijo, mientras que el segundo se corresponde con el valor de un Byte de memoria que contiene uno de los caracteres introducidos como número de serie):

- En la primera comparación se compara 'b' (código ASCII 62 en hexadecimal) con 'a' (código ASCII 61 en hexadecimal). El carácter 'a' es el que ocupa la posición 10 entre los caracteres introducidos como número de serie, por lo que 'b' es el décimo carácter del número de serie correcto.

- En la segunda comparación se compara 'y' (código ASCII 79 en hexadecimal) con '8' (código ASCII 38 en hexadecimal). El carácter '8' es el que ocupa la posición 8 entre los caracteres introducidos como número de serie, por lo que 'y' es el octavo carácter del número de serie correcto.

- En la tercera comparación se compara 'l' (código ASCII 6C en hexadecimal) con '6' (código ASCII 36 en hexadecimal). El carácter '6' es el que ocupa la posición 6 entre los caracteres introducidos como número de serie, por lo que 'l' es el sexto carácter del número de serie correcto.

- En la cuarta comparación se compara 'e' (código ASCII 65 en hexadecimal) con '4' (código ASCII 34 en hexadecimal). El carácter '4' es el que ocupa la posición 4 entre los caracteres introducidos como número de serie, por lo que 'e' es el cuarto carácter del número de serie correcto.

- En la quinta comparación se compara 'i' (código ASCII 69 en hexadecimal) con '2' (código ASCII 32 en hexadecimal). El carácter '2' es el que ocupa la posición 2 entre los caracteres introducidos como número de serie, por lo que 'i' es el segundo carácter del número de serie correcto.

- En la sexta comparación se compara 'l' (código ASCII 6C en hexadecimal) con '1' (código ASCII 31 en hexadecimal). El carácter '1' es el que ocupa la posición 1 entre los caracteres introducidos como número de serie, por lo que 'l' es el primer carácter del número de serie correcto.

- En la séptima comparación se compara 'k' (código ASCII 6B en hexadecimal) con '3' (código ASCII 33 en hexadecimal). El carácter '3' es el que ocupa la posición 3 entre los caracteres introducidos como número de serie, por lo que 'k' es el tercer carácter del número de serie correcto.

- En la octava comparación se compara 'o' (código ASCII 6F en hexadecimal) con '5' (código ASCII 35 en hexadecimal). El carácter '5' es el que ocupa la posición 5 entre los caracteres introducidos como número de serie, por lo que 'o' es el quinto carácter del número de serie correcto.

- En la novena comparación se compara 'l' (código ASCII 6C en hexadecimal) con '7' (código ASCII 37 en hexadecimal). El carácter '7' es el que ocupa la posición 7 entre los caracteres introducidos como número de serie, por lo que 'l' es el séptimo carácter del número de serie correcto.

- En la décima comparación se compara 'd' (código ASCII 64 en hexadecimal) con '9' (código ASCII 39 en hexadecimal). El carácter '9' es el que ocupa la posición 9 entre los caracteres introducidos como número de serie, por lo que 'd' es el noveno carácter del número de serie correcto.

- En la undécima comparación se compara 'g' (código ASCII 67 en hexadecimal) con 'b' (código ASCII 62 en hexadecimal). El carácter 'b' es el que ocupa la posición 11 entre los caracteres introducidos como número de serie, por lo que 'g' es el undécimo carácter del número de serie correcto.

Por tanto, el número de serie correctos es: "likeollydbg".

Para comprobar si lo he hecho bien ejecuto el programa e introduzco "likeollydbg" como número de serie:
Pulso 'Check serial' y se muestra el mensaje de éxito:
Y lo único que me falta para superar este reto es introducir el número de serie correcto en la plataforma:

Reversing(XIII): Solución Reto RingZer0 Team "RingZer0 Authenticator"

$
0
0
En este post la solución a otro de los retos de 'reversing' de la plataforma RingZer0 Team.

Este reto tiene el título "RingZer0 Authenticator" y mi valoración sobre su dificultad es:  ☆☆.

Su enunciado dice lo siguiente:

To improve the security of our site, contestants now need to authenticate using our RingZer0 Authenticator.

Y nos dan un archivo ejecutable (77b36c523c341967b8880240a5bee0a3.exe).

Solución: Ejecuto este fichero se me pide que introduzca un nombre de usuario y un código de autenticación:
Introduzco dos cadenas cualesquiera, por ejemplo: "Username" y "AuthCode", y se muestra un mensaje de error:
Como primera opción en este tipo de retos suelo utilizar OllyDbg. Abro en esta herramienta el archivo ejecutable y, también como suelo hacer habitualmente, empiezo el análisis buscando todas las cadenas de texto o 'strings' utilizadas en el programa (en la parte superior izquierda de la ventana principal: clic botón derecho, 'Search for' > 'All referenced strings'):
Y obtengo lo siguiente:
Como se observa en la figura anterior, entre las cadenas de texto está el mensaje de error que me ha aparecido al ejecutar el programa: "Wrong Authentication code"y también el mensaje que supuestamente aparecerá si introduzco los valores correctos"Authentication succesful!". Si hago doble clic sobre este último voy a la parte del código donde se encuentra esta cadena de texto:
Más arriba veo que, después de que se capturan los valores introducidos en el cuadro de texto ('textbox'), en 0x0040163D hay una llamada a una función. Si esa función devuelve 0 en el contenido del registro EAX la instrucción de salto condicional (JZ) que se se encuentra más abajo realizará un salto a 0x0040167C y se mostrará el mensaje de error.

Pongo un punto de interrupción en la instrucción CALL que llama a dicha función (clic botón derecho sobre ella, 'Breakpoint' > 'Toggle') para que cuando ejecute el programa en modo depuración ('debug mode') éste se detenga en esa instrucción:
Tras ejecutar el programa en modo depuración, 'Run debugged application (F9)'introduzco otra vez "Username" y "AuthCode", pulso 'Validate' el programa se detiene en el punto de interrupción que acabo de colocar.

Veo que el registro EBX contiene la dirección a partir de la que se ha incluido la cadena que he introducido como nombre de usuario ("Username") y que ESI contiene la dirección a partir de la que se almacena la cadena con el código de autenticación ("AuthCode"):
Ejecuto la CALL'Step into (F7)', entrando en el código de la función a la que llama para ejecutar instrucción a instrucción:
En 0x004014B8 veo una instrucción REPE CMPS, que comparará carácter a carácter las dos cadenas a las que apunten los registros ESI y EDI, respectivamente.

Como se ve, en la cuarta línea anterior a esa instrucción se moverá a EDI la dirección a partir de la cual se almacena la cadena "RingZer0", en la siguiente se inicializará a 9 el valor del registro ECX (el número de caracteres de la cadena "RingZer0" más uno) y en la siguiente se moverá al registro ESI la dirección a partir de la cual se almacena la cadena con el nombre de usuario que he introducido ("Username").

Por tanto, lo que se comparará carácter a carácter en la instrucción REPE CMPS son las dos cadenas indicadas en el párrafo anterior. Después de la comparación de cada par de caracteres el valor del registro ECX se decrementará en 1 y los valores contenidos en ESI y EDI se incrementarán en 1 para apuntar a los dos siguientes caracteres de sendas cadenas a comparar.

Las comparaciones de pares de caracteres finalizarán cuando un par de los caracteres comparados sean diferentes entre sí o cuando ECX sea igual a cero.

La instrucción JNE, que se encuentra en la siguiente línea de la instrucción REPE CMPS, producirá un salto a la instrucción en la dirección indicada si ambas cadenas son diferentes.

Para ver todo lo anterior pongo un punto de interrupción en la instrucción en cuestión:
Como se observa en las figuras anteriores, lo que se compara es el primer carácter de "RingZer0" ("R") con el primero del nombre de usuario que he introducido ("U") y ECX tiene el valor 9, y tras esta primera iteración, como "R" y "U" son diferentes, finalizan las comparaciones y se cede el control a la siguiente instrucción (JNE) que realizará un salto a una instrucción XOR que pone a cero el valor de EAX, y un poco más adelante finalizará la función.
Por tanto, en este caso y como ya he dicho antes, como tras la ejecución de esta función EAX valdrá 0 el programa mostrará el mensaje de error.

Tras este pequeño análisis de la primera validación que realiza el programa es evidente que para superar este reto el nombre de usuario que se introduzca debe ser "RingZer0", pero como veo más adelante sólo eso no es suficiente. Vuelvo a ejecutar el programa en modo depuración e introduzco "RingZer0" como nombre de usuario y "AuthCode" como código de autenticación:
Como se observa en las figuras anteriores, ahora lo que se compara es el primer carácter de "RingZer0" ("R") con el primero del nombre de usuario que he introducido ("R") y ECX tiene el valor 9, y tras esta primera iteración:
Las comparaciones de pares de caracteres continúan y, como se observa en las figuras anteriores, ECX se ha decrementado en 1 (tiene el valor 8), ESI y EDI se han incrementado en 1 (tienen valores 0x0061F6AB y 0x00405025, respectivamente) y en la segunda iteración lo que se compara es el siguiente carácter de "RingZer0" ("i") con el siguiente del nombre de usuario que he introducido ("i"), y así sucesivamente hasta la última comparación de pares de caracteres:
Una vez finalizada la ejecución de la instrucción REPE CMPS, como ambas cadenas son iguales, la siguiente instrucción (JNE) no producirá el salto y el programa continuará con la siguiente validación:
En 0x004014C5 veo una instrucción REPNE SCAS, que buscará el contenido de AL (que será cero, ya que AX se pone a ese valor antes de ejecutarse esa instrucción) en cada uno de los caracteres de la cadena a la que apunte el registro EDI (el código de autenticación introducido, ya que en la instrucción anterior se moverá a este registro la dirección a a partir de la que se almacena dicho código).

Como se ve en la segunda línea anterior a esa instrucción, el valor del registro ECX se inicializará a -1 y después de la búsqueda en cada carácter este valor se decrementará en 1, y el valor contenido en EDI se incrementará en 1 para a puntar al siguiente carácter de la cadena ("AuthCode") en el que buscar.

Para verlo exactamente pongo un punto de interrupción en la instrucción en cuestión:
Como se observa en las figuras anteriores, se busca 0 en el primer carácter de "AuthCode" ("A", código ASCII en hexadecimal 41) y ECX tiene el valor -1 en decimal (se representa como 0xFFFFFFFF), y tras esta primera iteración:
La búsqueda continúa y, como se observa en las figuras anteriores, ECX se ha decrementado en 1 (tiene el valor -2 en decimal, que se representa como 0xFFFFFFFE), EDI se ha incrementado en 1 (tiene valor 0x0061F6BC) y en la segunda iteración lo que se busca es 0 en el siguiente carácter de "AuthCode" ("u", código ASCII en hexadecimal 75). Y así sucesivamente hasta la última búsqueda:
Una vez finalizada la ejecución de la instrucción REPNE SCAS, el registro ECX tiene valor -10 en decimal (se representa como 0xFFFFFFF6):
Posteriormente se pondrá a cero el registro EAX y se comparará ECX (-10 en decimal, que se representa como 0xFFFFFFF6) con el valor -17 en decimal (se representa como 0xFFFFFFEF), y, como ambos valores son distintos, la siguiente instrucción (JE) no producirá el salto y finalizará la función:
Por tanto, en este caso y como ya he dicho antes, como tras la ejecución de esta función EAX valdrá 0 el programa mostrará el mensaje de error.

Tras este pequeño análisis de la segunda validación que realiza el programa deduzco que para superar este reto, además de que hay que introducir "RingZer0" como nombre de usuario, el código de autenticación tiene que tener una longitud de 15 caracteres, pero como veo más adelante tampoco estas dos cosas son sólo suficientes para ello. Reinicio la ejecución en modo depuración e introduzco "RingZer0" como nombre de usuario y "AuthCode1234567" como código de autenticación:
Ahora, como se observa en las figuras anteriores, una vez finalizada la ejecución de la instrucción REPNE SCAS, el registro ECX tiene valor -17 en decimal (se representa como 0xFFFFFFEF), que se compara con ese mismo valor, y, como evidentemente ambos son iguales, la siguiente instrucción (JE) producirá el salto y el programa continuará con la siguiente validación.

En esta tercera validación lo que se comprobará es que los 15 caracteres del código de autenticación introducido sean dígitos (del 0 al 9). Para ello, se restará 30 en hexadecimal (código ASCII de cero) al código ASCII en hexadecimal de cada carácter del código de autenticación introducido y si el resultado es mayor que 9 significa que ese carácter no es un dígito.

En nuestro caso he incluido caracteres alfabéticos en el código de autenticación que he introducido ("AuthCode1234567"), por lo que está validación no se superará, finalizará la función y el programa mostrará el mensaje de error, pero para verlo exactamente pongo un punto de interrupción en la instrucción que comprueba cada uno de los caracteres del código de autenticación para asegurarse que todos ellos son dígitos:
Como se observa en las figuras anteriores, el resultado de restar 30 en hexadecimal (código ASCII de cero, en decimal 48) al código ASCII del primer carácter del código de autenticación introducido ("A", cuyo código ASCII es 41 en hexadecimal, 65 en decimal) es 11 en hexadecimal (17 en decimal), y como éste es mayor que 9 en hexadecimal (9 en decimal) la siguiente instrucción (JA) producirá un salto a una instrucción XOR que pondrá a cero el valor de EAX, y un poco más adelante finalizará la función:
Por tanto, en este caso y como ya he dicho antes, como tras la ejecución de esta función EAX valdrá 0 el programa mostrará el mensaje de error.

Tras este pequeño análisis de la tercera validación que realiza el programa es evidente que para superar este reto, además de que hay que introducir "RingZer0" como nombre de usuario y el código de autenticación tiene que tener una longitud de 15 caracteres, los caracteres del código de autenticación tienen que ser todos ellos dígitos (del 0 al 9), pero como veo más adelante tampoco estas tres cosas son por sí solas suficientes para ello. Vuelvo a ejecutar el programa en modo depuración e introduzco "RingZer0" como nombre de usuario y "123456789012345" como código de autenticación:
Ahora, como se satisfacen las tres primeras validaciones, el programa continuará con la siguiente validación. Pongo un punto de interrupción en la instrucción CMP que hay justo antes de la instrucción de salto condicional JE, para ver que dos valores se compararán:
Se compara el valor 98 en hexadecimal (152 en decimal) con 0F en hexadecimal (15 en decimal) y la siguiente instrucción (JE), como ambos valores son distintos, no producirá el salto, se pondrá EAX a cero, se finalizará la función y el programa mostrará el mensaje de error.

Para evitar esto modifico el contenido de la dirección a la que apunta EAX y pongo como valor 98 en hexadecimal (152 en decimal) en lugar de 0F en hexadecimal (15 en decimal):
Como se observa en las figuras anteriores, ahora el salto se producirá. Pongo un punto de interrupción en cada una de las cuatro instrucciones de comparación que se ven a continuación, controlando los saltos condicionales tras su ejecución, para ver que valores se compararán:
- En la comparación en 0x401517 se compara el valor 97 en hexadecimal (151 en decimal) con 61 en hexadecimal (97 en decimal).
- En la comparación en 0x40151F se compara el valor 78 en hexadecimal (120 en decimal) con  en hexadecimal ( en decimal).
- En la comparación en 0x401527 se compara el valor 0F en hexadecimal (15 en decimal) con 49 en hexadecimal (73 en decimal).
- En la comparación en 0x40152F se compara el valor 15 en hexadecimal (21 en decimal) con 9B en hexadecimal (155 en decimal).

Si los valores comparados en cada una de estas cinco instrucciones CMP son iguales se pondrá EAX a uno, se finalizará la función y el programa mostrará el mensaje de éxito:
Por tanto, para conocer el código de autenticación correcto necesito saber cómo se obtienen los valores que se compararán en las cinco instrucciones CMP anteriores.

Para ello, vuelvo a ejecutar el programa en modo depuración (introduzco "RingZer0" como nombre de usuario y "123456789012345" como código de autenticación) y pongo un punto de interrupción en la instrucción en 0x004014FA que llama a una función:
Ejecuto una a una en modo depuración las instrucciones de esta función y veo que se toman de 3 en 3 los dígitos del código de autenticación que he introducido, y con cada grupo de 3 dígitos (d1, d2, d3) se realiza lo siguiente:

(1) d1 * 186 en decimal (BA en hexadecimal).
(2) 48 en decimal (30 en hexadecimal) - d2.
(3) d3 * 13 en decimal (D en hexadecimal).

El resultado para cada grupo de 3 dígitos es el Byte menos significativo de: (1) + (2) + (3).

Una vez finalizada la ejecución de esta función el resultado obtenido para cada uno de los 5 grupos de 3 dígitos se comparará con un cierto valor (en la ejecución en modo depuración de las 5 instrucciones de comparación he visto que estos valores, en decimal, eran: 152, 151, 120, 15 y 21, respectivamente) con objeto de mostrar el mensaje de error si alguno de ellos es distinto o el de éxito en el caso de que todos ellos coincidan.

Para ver si estoy en lo cierto, calculo el código de autenticación que me debería permitir pasar esta última validación. Para ello creo un pequeño script en python:

x=[152,151,120,15,21]
print ''
for i in range(len(x)):
    grupo=[]
    for d1 in range(0, 10):
        for d2 in range(0, 10):
            for d3 in range(0, 10):
                y=(d1*186 + 48 - d2 + d3*13) & 0xff
                if y==x[i]:
                   grupo.append(str(d1)+str(d2)+str(d3))

    print '[+]',i+1,grupo

Ejecuto este scritpt y obtengo:
Lógicamente, existen varios números de autenticación que satisfacen esta cuarta y última validación. En la figura anterior se muestran las elementos válidos de tres dígitos correspondientes a cada uno de los 5 grupos en los que se divide el código de autenticación, es decir, cualquier combinación de un elemento del grupo 1 con uno del 2, más otro del 3, más otro del 4 y más otro del 5 formará un código de autenticación válido.

Para comprobar si esto es así escojo, por ejemplo, el elemento "375" del grupo 1, "621" del grupo 2, "724" del grupo 3, "967" del grupo 4 y "852" del grupo 5. Concateno todos ellos y obtengo como número de autenticación: "375621724967852", y ejecuto el programa, introduzco "RingZer0" como nombre de usuario y como código de autenticación el indicado anteriormente, pulso "Validate"y se muestra el mensaje de éxito:
Para superar este reto en la plataforma introduzco ese mismo código de autenticación y pulso 'Authenticate', con lo que se mostrará la flag:
Y, finalmente, envío dicha flag:

Reversing(XIV): Solución Reto PoliCTF "Crack me if you can"

$
0
0
En este post la solución a un reto de 'reversing' de un archivo APK (Android Application Package), es decir, de una aplicación para el sistema operativo Android.

Este reto tiene el título "Crack me if you can" y mi valoración sobre su dificultad es:  ☆☆☆.

Su enunciado dice lo siguiente:

John bets nobody can find the passphrase to login!

Y nos dan un archivo cifrado (crack-me-if-you-can_d4e396383e3f64ec7698efaf42f7f32b.tar.gz.gpg) y la clave de cifrado (GPG Key: viphHowrirOmbugTudIbavMeuhacyet').

Solución: Al descifrar el archivo que nos dan obtengo el archivo APK (crack-me-if-you-can.apk) y lo ejecuto en un emulador de Android.

La aplicación me pide que introduzca una contraseña, introduzco una cualquiera ("passphrase"), pulso "Sign in" y se muestra un mensaje de error:
Para el análisis de la APK utilizo jadx, un decompilador de archivos APK.

Al examinar las clases veo:
Es decir, se realizan diversos reemplazos a la siguiente cadena:

[[c%l][c{g}[%{%Mc%spdgj=]T%aat%=O%bRu%sc]c%ti[o%n=Wcs%=No[t=T][hct%=buga[d=As%=W]e=T%ho[u%[%g]h%t[%}%

Y si el resultado de los reemplazos realizados es igual a la contraseña introducida se muestra el mensaje de éxito: "Good to go! =)".

Para obtener la contraseña correcta creo un script en Python para realizar esos mismos reemplazos a dicha cadena:

flag = '[[c%l][c{g}[%{%Mc%spdgj=]T%aat%=O%bRu%sc]c%ti[o%n=Wcs%=No[t=T][hct%=buga[d=As%=W]e=T%ho[u%[%g]h%t[%}%'

flag = flag.replace('spdgj', 'yb%e')
flag = flag.replace('aat', 'his')
flag = flag.replace('buga', 'Goo')
flag = flag.replace('=', '_')
flag = flag.replace('\\}', '', 1)
flag = flag.replace('\\{', '', 1)
flag = flag.replace('R', 'f', 1)
flag = flag.replace('c', 'f', 1)
flag = flag.replace(']', '')
flag = flag.replace('[', '')
flag = flag.replace('%', '')
flag = flag.replace('c', 'a')
flag = flag.replace('aa', 'ca')

print 'Flag:', flag

Ejecuto este script:
Por tanto, la solución a este reto es:
flag{Maybe_This_Obfuscation_Was_Not_That_Good_As_We_Thought}

Para comprobar si lo he hecho bien, ejecuto la APK en el emulador de Android, introduzco la contraseña y pulso "Sign in":
Y, tal y como se puede ver en la figura anterior, se muestra el mensaje de éxito.

Reversing(XV): Solución Reto Sharif University "Commercial Application"

$
0
0
En este post la solución a otro reto de 'reversing' de un archivo APK (Android Application Package), es decir, de una aplicación para el sistema operativo Android, en el que también se ve involucrada la criptografía.

Este reto tiene el título "Commercial Application" y mi valoración sobre su dificultad es:  .

Su enunciado dice lo siguiente:

Flag is a serial number.

Y nos dan un archivo APK (suCTF.apk).

Solución: Ejecuto la aplicación en un emulador de Android

Si pulso sobre "Picture-01" se muestra el mensaje "You picked : 1", mientras que si pulso sobre "Picture-02" o "Picture-03" el mensaje que se visualiza es "Please enter a registration key to view all items".
Y si pulso sobre el icono indicado en la primera de las figuras siguientes se me pide que introduzca una clave de producto. Introduzco una cualquiera (por ejemplo: "ClaveDeProducto"), pulso "Continue" y se muestra un mensaje de error:
Al igual que en el post anterior de 'reversing'para el análisis de la APK utilizo jadx, un decompilador de archivos APK.

Examinando las clases veo que parece que la validación de la clave de producto que se introduce se realiza en la clase "KeyVerifier" mediante su función "IsValidLicenceKey", que utiliza como parámetros "userInput" (clave de producto introducida), "secretKey" (clave con la que se ha cifrado el número de licencia válido con el algoritmo AES en modo CBC - 'cipher-block chaining' -) y "iv" (vector de inicialización):
Es decir, dicha función verifica que la clave de producto introducida por el usuario (que se cifra con AES en modo CBC utilizando "secretKey" como clave de cifrado y "iv" como vector de inicialización) sea igual que el número de licencia válido (VALID_LICENCE, que está cifrado de igual manera), por lo que parece que no hay otra solución que descifrar este último.

Para ello necesito saber los parámetros 2 y 3 que recibe esta función, "secretKey" y "iv", respectivamente. Pero, ¿dónde se establecen esos valores?.

Examinando la clase "DBHelper" veo lo siguiente:
De lo que deduzco que los valores de "secretKey" y "iv" se obtienen de tabla "config" en la base de datos db.db tras realizarse una consulta ("SELECT") a dicha tabla seleccionando la fila con el valor de la columna "a" igual 1. En concreto, los valores buscados serían los correspondientes a la sexta y quinta columnas, respectivamente.

Extraigo el contenido del archivo APK con el software 7-Zip y veo que en la carpeta assets se encuentra la base de datos SQLite utilizada por la aplicación (db.db). Abro esa base de datos con el software DB Browser for SQLite y veo que la tabla "config" contiene una única fila con los siguientes valores en sus columnas: 
Y, por tanto, ya tengo los valores de la clave de cifrado ("secretKey") y del vector de inicialización ("iv"), columnas "f" (sexta columna) y "e" (quinta columna), respectivamente:

secretKey = 37eaae0141f1a3adf8a1dee655853714
iv = a5efdbd57b84ca36

Ahora ya sólo queda descifrar el número de licencia válido:

VALID_LICENCE = 29a002d9340fc4bd54492f327269f3e051619b889dc8da723e135ce486965d84

Para ello creo un pequeño script en python:

from Crypto.Cipher import AES

VALID_LICENCE = '29a002d9340fc4bd54492f327269f3e051619b889dc8da723e135ce486965d84'.decode('hex')
secretKey = '37eaae0141f1a3adf8a1dee655853714'.decode('hex')
iv = 'a5efdbd57b84ca36'
decryption = AES.new(secretKey, AES.MODE_CBC, iv)
VALID_LICENCE_DEC = decryption.decrypt(VALID_LICENCE)

print ''
print 'VALID LICENCE:', VALID_LICENCE_DEC

Lo ejecuto y obtengo lo siguiente:
Con lo que la solución a este reto es: fl-ag-IS-se-ri-al-NU-MB-ER.

Para comprobar si lo he hecho bien ejecuto la APK en el emulador de Android, introduzco el número de licencia hallado en el paso anterior y pulso "Continue":
Y, tal y como se puede ver en la figura anterior, se muestra el mensaje de éxito.

Reversing(XVI): Solución Retos "Android CrackMe" (I)

$
0
0
Con esta entrada comienzo una serie de posts con las soluciones a retos de 'reversing' de aplicaciones para el sistema operativo Android que he ido encontrando en diversas páginas web, plataformas de CTF online, etc. y que cumplen con presentar una dificultad para su resolución muy asequible (muy fáciles o fáciles), además de que me han parecido interesantes y/o entretenidos.

En este primer post indico mi solución a uno de los retos que se plantean en https://github.com/reoky/android-crackme-challenge, en concreto al segundo de los retos.
Challenge Two:

Nos dan un archivo APK (crackme_two.apk) y tenemos que autenticarnos en la aplicación mediante un e-mail y una contraseña.

Mi valoración sobre su dificultad es:  ☆☆☆.

Solución: ejecuto la aplicación en un emulador de Android y se me pide que me autentique. Introduzco un e-mail y una contraseña cualesquiera, pulso "Authenticate" y se muestra un mensaje de error:
Para el análisis de la APK utilizo jadx, un decompilador de archivos APK.

Examinando las clases enseguida veo que la validación del e-mail y de la contraseña que se introducen se realiza en la clase "ChallengeTwoFragmentOnClickListener":
Tal y como se observa en el código de la figura anterior, para autenticarse con éxito el campo "Email" debe ser "manager@corp.net" y, además, se calcula el hash md5 de lo que se introduce en el campo "Secret", que debe ser igual a "b2c4782f0afc0d9ccf21af70ac6c5c7e".

Por tanto, para superar este reto tenemos que revertir dicho hash md5, para lo que acudo a una de las muchas páginas especializadas en el cálculo de hashes a ver si tengo suerte:
Por tanto, la contraseña es: zipdrive.

Para comprobar si lo he hecho bien ejecuto la APK en el emulador de Android, introduzco los campos "Email" y "Secret" hallados y pulso "Authenticate":
Y, tal y como se puede ver en la figura anterior, se muestra el mensaje de éxito.
Viewing all 639 articles
Browse latest View live