![CDATA[
tryme
El primer desafío emplea una variante de codificación Base64. El binario utiliza un alfabeto propio de 64 símbolos y, tras la codificación, aplica XOR con el valor 0x02 a cada byte. Para rceuperar la bandera se invierte el proceso: aplicar XOR 2 al buffer y decodificar usando la tabla modificada.
import base64
ALFABETO_BINARIO = "..." # 64 símbolos extraídos del ejecutable
ALFABETO_STD = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
def decodificar_tryme(datos):
tr = str.maketrans(ALFABETO_BINARIO, ALFABETO_STD)
temp = datos.translate(tr)
dec = base64.b64decode(temp)
return bytes(b ^ 2 for b in dec)
Los hackers no lloran
Este reto se apoya en CUDA. El programa configura tensores para vectores de cuatro y ocho elementos. La comprobación convierte cada carácter de la entrada con la expresión (carácter × 1.020123456789) + 1, multiplica el resultado por un arreglo double y lo compara con un segundo arreglo.
La comparación presenta un desfase constante cercano a 100, por lo que se compensa restando dicho valor antes de invertir la operación.
muestras = [
(4358.58716, 60.51846366284686),
(6122.2983, 89.4737043286176),
(2158.74574, 24.03104711352393),
(5973.017537, 84.68873702464015),
(9173.840881, 104.6695364464632),
(6164.67827, 83.75627693648984),
(12293.528276, 96.41044018110416),
(4091.327439, 75.27071882034213),
(3360.696562, 60.33140727998576),
(2403.667017, 46.10475987767577),
(3199.455077, 56.28563000222285),
(4962.117508, 86.68936481373537),
(8266.407604, 80.87786332435297),
(2863.062918, 55.29894355978243),
(1044.626306, 9.261748448423328),
(1067.530873, 20.6272127322797),
(3217.476319, 31.1897419717479),
(6260.942959, 116.1865600512257),
(3278.952568, 30.85991826286804),
(160.724197, 1.063344600421732),
(596.797742, 10.59144776777723),
(3277.973032, 55.64965261721374),
(6368.757598, 122.950447694522),
(842.858109, 7.140637105592679),
(5925.142209, 55.44977106531295),
(3046.937162, 62.82703886751251),
(12752.384458, 125.3057489450499),
(2442.54747, 45.94487116254584),
(1827.164764, 32.57185367060958),
(4903.961921, 92.37291765689986),
(5619.869598, 117.6805078353046),
(3851.247916, 63.42241478603398),
(4472.987644, 84.08593452538155),
(13135.636855, 125.3035418960081),
(1640.630636, 26.50460072585211),
(975.429551, 15.6085145259943),
(2174.379531, 35.68707511621358),
(2289.845471, 37.67352051379848),
(2605.707441, 24.32434117146088),
(1488.586824, 25.69248490815507),
(12216.019619, 116.4638282572803),
(4588.270425, 86.30264794289376),
(4803.36317, 79.51984419851664),
(13035.30263, 100.6517460100543),
]
DESPLAZAMIENTO = 100.0
FACTOR = 1.020123456789
bandera = []
for esperado, divisor in muestras:
normalizado = (esperado - DESPLAZAMIENTO) / divisor
recuperado = int(normalizado / FACTOR - 0.5)
bandera.append(chr(recuperado))
print("".join(bandera))
La ejecución del script produce la bandera: DASCTF{34056b0c-a3d7-71ef-b132-92e8688d4e29}.
Estereotipo RE
El análisis estático muestra la función de cifrado en sub_401740, pero en 0x401795 hay una instrucción que manipula el retorno de pila y hace que IDA descarte el código posterior. Sustituyendo esos bytes por NOP se revela el flujo completo.
La primera capa es XTEA combinada con XOR, pero genera una bandera falsa: fakeflag_plz_Try_more_hard_to_find_the_true_flag. Al inspeccionar el ejecutable se detecta un TLS callback con lógica adicional y código auto-modificante (SMC) en sub_41F000.
El algoritmo real es XXTEA con clave {What_is_this_?} y delta 0x11451419. Tras cifrar se aplica un XOR con una constante de 64 bytes y se compara con la cadena objetivo. La solución descifra primero con XXTEA, luego con XTEA usando otra clave, intercalando las operaciones XOR.
import struct
DELTA_XTEA = 0x9E3779B9
DELTA_XXTEA = 0x11451419
def bloque_xtea(par, clave, rondas=32):
v0, v1 = par
suma = (DELTA_XTEA * rondas) & 0xFFFFFFFF
for _ in range(rondas):
v1 = (v1 - ((((v0 << 4) ^ (v0 >> 5)) + v0) ^ (suma + clave[(suma >> 11) & 3]))) & 0xFFFFFFFF
suma = (suma - DELTA_XTEA) & 0xFFFFFFFF
v0 = (v0 - ((((v1 << 4) ^ (v1 >> 5)) + v1) ^ (suma + clave[suma & 3]))) & 0xFFFFFFFF
return [v0, v1]
def mezcla_xxtea(z, y, suma, clave, pos, e):
return ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((suma ^ y) + (clave[(pos & 3) ^ e] ^ z)))
def descifrar_xxtea(bloques, clave):
n = len(bloques)
rondas = 6 + 52 // n
suma = (rondas * DELTA_XXTEA) & 0xFFFFFFFF
y = bloques[0]
for _ in range(rondas):
e = (suma >> 2) & 3
for p in range(n - 1, 0, -1):
z = bloques[p - 1]
bloques[p] = (bloques[p] - mezcla_xxtea(z, y, suma, clave, p, e)) & 0xFFFFFFFF
y = bloques[p]
z = bloques[-1]
bloques[0] = (bloques[0] - mezcla_xxtea(z, y, suma, clave, -1, e)) & 0xFFFFFFFF
y = bloques[0]
suma = (suma - DELTA_XXTEA) & 0xFFFFFFFF
return bloques
def xor_bloques(a, b):
return bytes(x ^ y for x, y in zip(a, b))
if __name__ == '__main__':
clave1 = [struct.unpack('<i b="" bloques="[struct.unpack('<I'," bytes.fromhex="" clave1="" clave2="[0x756F797B," datos="bytes.fromhex('18091C14371D162D3C05163E0203102C0E313915043A39030D132B3E06083700170B001D1C0016060717300330060A71')" for="" i="" in="" intermedio="b''.join(struct.pack('<I'," len="" print="" range="" resultado="b''" struct.pack="" v="" v0="" v1="bloque_xtea(bloques[i:i+2],"></i>
La salida final es: DASCTF{You_come_to_me_better_than_all_the_good.}
secret_of_inkey
Partiendo de la cadena del cuadro de diálogo se localiza la función sub_417240. El verdadero procesamiento ocurre en sub_416D70: para cada bloque de 32 bytes aplica XOR byte a byte con el índice y la clave introducida, y después descifra con AES en modo ECB usando esa misma clave.
La estructura forma una cadena de claves: descifrar un bloque revela la siguiente clave, que a su vez permite descifrar más bloques. El siguiente script itera hasta obtener todas y extrae el texto legible.
from Crypto.Cipher import AES
import re
datos = bytes.fromhex('C9EFAB9D79AEA0435699970D9CBDF02989D26F51BCC425EEFCFFD46B7E86C2FCA1D7544BA64FBD757CE9206... datos omitidos ...EACE0E6AFDCA5FA7B327BE42CEA6A6F9905AF097C2DC4C4A6DAFE94955B4368DF1E8AE87A5A5E47C6E9ACF6E092F9179719422')
conocidas = {'565': '9fc82e15d9de6ef2'}
bloques = [datos[i:i+32] for i in range(0, len(datos), 32)]
activo = 1
while activo:
activo = 0
for etiqueta in list(conocidas.keys()):
texto = conocidas[etiqueta]
cifrado = AES.new(texto.encode(), AES.MODE_ECB)
for bloque in list(bloques):
temp = bytearray(bloque)
for i in range(32):
temp[i] ^= i ^ ord(texto[i % 16])
plano = cifrado.decrypt(bytes(temp))
if plano[:6] == b'key_of':
coincidencia = re.findall(r'key_of_(\d+)_is_"([0-9a-f]+)"', plano.decode())
if coincidencia:
idx, nueva = coincidencia[0]
if idx not in conocidas:
conocidas[idx] = nueva
bloques.remove(bloque)
activo = 1
elif b'nothing_here' in plano:
bloques.remove(bloque)
activo = 1
for etiqueta in list(conocidas.keys()):
texto = conocidas[etiqueta]
cifrado = AES.new(texto.encode(), AES.MODE_ECB)
for bloque in bloques:
temp = bytearray(bloque)
for i in range(32):
temp[i] ^= i ^ ord(texto[i % 16])
plano = cifrado.decrypt(bytes(temp))
if all(0x20 <= b <= 0x7e for b in plano):
print(etiqueta, plano.decode())
Entre los bloques legibles se obtiene la bandera: DASCTF{Do_y0u_l1ke_wh4t_y0u_s3e}.
]]>