Intro

Hoy tenemos un crackme hecho en ensamblador y que cuenta con tres niveles. En el primero de todos nos enfrentamos a una «Splash screen» o nag. El segundo en un serial Hardcodeado y el tercero un número de serie asociado a un nombre.

Nopeando la Splash Screen

splashscreen

Abrimos el crackme con Olly y vamos a las «Intermodular Calls«, enseguida vemos la función que crea las ventanas «CreateWindowExA«. Se puede ver lo que parece ser la creación de la pantalla del crackme y al final hay algo que salta a la vista y es la propiedad «WS_TOPMOST», es decir, que se mantenga delante del resto de ventanas.

intermodularcalls

Pinchamos sobre la función y vamos a parar aquí.

codesplash

Vemos la llamada a CreateWindowExA que podríamos parchear pero vamos a pensar un poco. Vemos la función GetTickCount y que carga el valor 7D0. 7D0 es 2000 en decimal, que perfectamente pueden ser milisegundos, por lo tanto el parcheo más elegante sería poner la función GetTickCount a 0. En la imagen inferior se puede ver como queda parcheado el valor 7D0.

splashtime

splashparcheada

Probamos y funciona, pasamos a lo siguiente.

Serial Hardcodeado

El mensaje de error del serial hardcodeado dice «Sorry, please try again». Lo buscamos en las string references y vamos a parar aquí.

hardcoded

Vemos un bucle de comparación que carga unos bytes de la memoria, los bytes dicen «HardCoded«, probamos y prueba superada.

hardcoded2

09-09-2014 11-12-42

El nombre y número de serie

Con el mismo método de las string references localizamos el código que nos interesa. Metemos deurus como nombre y 12345 como serial y empezamos a tracear. Lo primero que hace es una serie de operaciones con nuestro nombre a las que podemos llamar aritmética modular. Aunque en la imagen viene bastante detallado se vé mejor con un ejemplo.

buclenombre

Ejemplo para Nombre: deurus

d   e   u   r   u   s
64  65  75  72  75  73 -hex
100 101 117 114 117 115 -dec

1ºByte = ((Nombre[0] % 10)^0)+2
2ºByte = ((Nombre[1] % 10)^1)+2
3ºByte = ((Nombre[2] % 10)^2)+2
4ºByte = ((Nombre[3] % 10)^3)+2
5ºByte = ((Nombre[4] % 10)^4)+2
6ºByte = ((Nombre[5] % 10)^5)+2

1ºByte = ((100 Mod 10) Xor 0) + 2
2ºByte = ((101 Mod 10) Xor 1) + 2
3ºByte = ((117 Mod 10) Xor 2) + 2
4ºByte = ((114 Mod 10) Xor 3) + 2
5ºByte = ((117 Mod 10) Xor 4) + 2
6ºByte = ((115 Mod 10) Xor 5) + 2

Si el byte > 10 --> Byte = byte - 10

1ºByte = 2
2ºByte = 2
3ºByte = 7
4ºByte = 9
5ºByte = 5
6ºByte = 2

 Lo que nos deja que los Bytes mágicos para deurus son: 227952.

Debido a la naturaleza de la operación IDIV y el bucle en general, llegamos a la conclusión de que para cada letra es un solo byte mágico y que este está comprendido entre 0 y 9.

A continuación realiza las siguientes operaciones con el serial introducido.

bucleserial

Ejemplo para serial: 12345

1  2  3  4  5
31 32 33 34 35 -hex
49 50 51 52 53 -dec

49 mod 10 = 9
50 mod 10 = 0
51 mod 10 = 1
52 mod 10 = 2
53 mod 10 = 3

Los bytes mágicos del serial son: 90123, que difieren bastante de los conseguidos con el nombre.

A continuación compara byte a byte 227952 con 90123.

buclecompara

En resumen, para cada nombre genera un código por cada letra y luego la comprobación del serial la realiza usando el módulo 10 del dígito ascii. Lo primero que se me ocurre es que necesitamos cotejar algún dígito del 0 al 9 para tener cubiertas todas las posibilidades. Realizamos manualmente mod 10 a los números del 0 al 9 y obtenemos sus valores.

(0) 48 mod 10 = 8
(1) 49 mod 10 = 9
(2) 50 mod 10 = 0
(3) 51 mod 10 = 1
(4) 52 mod 10 = 2
(5) 53 mod 10 = 3
(6) 54 mod 10 = 4
(7) 55 mod 10 = 5
(8) 56 mod 10 = 6
(9) 57 mod 10 = 7

Con esto ya podríamos generar un serial válido.

0123456789 - Nuestro alfabeto numérico

8901234567 - Su valor Mod 10

Por lo que para deurus un serial válido sería: 449174. Recordemos que los bytes mágicos para deurus eran «227952», solo hay que sustituir.

Para realizar un KeyGen más interesante, he sacado los valores de un alfabeto mayor y le he añadido una rutina aleatoria para que genere seriales diferentes para un mismo nombre.

keygen

        'abcdefghijklmnñppqrstuvwxyz0123456789ABCDEFGHIJKLMNÑOPQRSTUVWXYZ - Alfabeto
        '7890123456778901234567789018901234567567890123455678901234556880 - Valor
        Dim suma As Integer = 0
        'Para hacer el serial más divertido
        Dim brute() As String = {"2", "3", "4", "5", "6", "7", "8", "9", "0", "1"}
        Dim brute2() As String = {"d", "e", "f", "g", "h", "i", "j", "a", "b", "c"}
        Dim brute3() As String = {"P", "Q", "R", "S", "T", "U", "j", "a", "D", "E"}
        Dim alea As New Random()
        txtserial.Text = ""
        'Evito nombres mayores de 11 para evitar el BUG comentado en le manual
        If Len(txtnombre.Text) > 0 And Len(txtnombre.Text) < 12 Then
            For i = 1 To Len(txtnombre.Text)
                Dim aleatorio As Integer = alea.Next(0, 9)
                suma = (((Asc(Mid(txtnombre.Text, i, 1))) Mod 10) Xor i - 1) + 2
                If suma > 9 Then
                    suma = suma - 10
                End If
                If (aleatorio) >= 0 And (aleatorio) <= 4 Then
                    txtserial.Text = txtserial.Text & brute(suma)
                ElseIf (aleatorio) > 4 And (aleatorio) <= 7 Then
                    txtserial.Text = txtserial.Text & brute2(suma)
                ElseIf (aleatorio) > 7 And (aleatorio) <= 10 Then
                    txtserial.Text = txtserial.Text & brute3(suma)
                End If
                suma = 0
            Next
        Else
            txtserial.Text = "El Nombre..."
        End If

Notas finales

Hay un pequeño bug en el almacenaje del nombre y serial y en el guardado de bytes mágicos del serial. Si nos fijamos en los bucles del nombre y el serial, vemos que los bytes mágicos del nombre los guarda a partir de la dirección de memoria 403258 y los bytes mágicos del serial a partir de 40324D. En la siguiente imagen podemos ver seleccionados los 11 primeros bytes donde se almacenan los bytes mágicos del serial. Vemos que hay seleccionados 11 bytes y que el siguiente sería ya 403258, precisamente donde están los bytes mágicos del nombre. Como puedes imaginar si escribes un serial >11 dígitos se solapan bytes y es una chapuza, de modo que el keygen lo he limitado a nombres de 11 dígitos.

dumpespacioserialhash

Links


Intro Hoy tenemos aquí un crackme del 2009 originario de crackmes.de. El Crackme está hecho en VB6, sin empacar y
Warning: This challenge is still active and therefore should not be resolved using this information. Aviso: Este reto sigue en
Intro Es un crackme realizado en ensamblador y en el que el objetivo es remover la NAG de la forma
AVISO: Debido a que este reto está en activo no publicaré a donde pertenece. En este pequeño CrackMe se nos

El reto consiste en dos imágenes (v1.png y v2.png) que, a simple vista, parecen contener ruido aleatorio. Sin embargo, ambas forman parte de un sistema de criptografía visual en la que cada imagen contiene información parcial que no es interpretable por separado, pero que al combinarse correctamente revelan información oculta.

La trampa está en que la combinación no se hace con operaciones normales como suma, resta o multiplicación. El autor del reto espera que el jugador use una herramienta como StegSolve y pruebe distintas operaciones tipo XOR, AND o MUL hasta encontrar una transformación en la que uno de los métodos muestre algo significativo. El truco está en llegar a la conclusión de que una de las imágenes hay que invertirla antes de combinar ambas imágenes. Todo esto se puede hacer con StegSolve sin necesidad de utilizar ninguna herramienta adicional, pero voy a aprovechar para hacerlo con python y así de paso entendemos como realiza las operaciones StegSolve. En resumen, para resolver el reto basta con:

  1. Invertir (Colour Inversion XOR) una de las imágenes.
  2. Combinar ambas imágenes mediante Analyse > Combine images.
  3. Operación MUL del combinador.

La operación MUL no es una multiplicación normalizada, sino una multiplicación de enteros de 24 bits (0xRRGGBB) con overflow, algo que la mayoría de herramientas no replican correctamente.

¿Por qué aparece la solución con esa combinación

Las imágenes están preparadas para que ciertos bits de color en una imagen sean el complemento de los de la otra. Por tanto:

  • Si se muestran tal cual → parecen ruido
  • Si se combinan mediante XOR → parte de la estructura aparece, pero no se ve el resultado correcto
  • Si se combinan mediante MUL «normal» → tampoco aparece
  • Si se aplica la multiplicación bitwise exacta usada por StegSolve → se alinean las partes ocultas

La operación MUL de StegSolve no es una multiplicación de píxeles, es decir, no hace:

R = (R1 * R2) / 255

sino:

c1 = 0xRRGGBB  (pixel 1)
c2 = 0xRRGGBB  (pixel 2)
resultado = (c1 * c2) & 0xFFFFFF

Con todo esto claro, he preparado un script para combinar las imágenes de forma automática.

import os
import numpy as np
from PIL import Image

# =========================================================
# UTILIDADES
# =========================================================

def ensure_output():
    if not os.path.exists("output"):
        os.makedirs("output")

def load_rgb(path):
    img = Image.open(path).convert("RGB")
    return np.array(img, dtype=np.uint32)

def save_rgb(arr, name):
    Image.fromarray(arr.astype(np.uint8), "RGB").save(os.path.join("output", name))

def invert_xor(arr):
    """Colour Inversion (Xor) de StegSolve."""
    out = arr.copy()
    out[..., :3] = 255 - out[..., :3]
    return out

# =========================================================
# FUNCIONES DE COMBINER EXACTAS DE STEGSOLVE
# =========================================================

def to24(arr):
    """Convierte RGB → entero 0xRRGGBB."""
    return ((arr[..., 0] << 16) |
            (arr[..., 1] << 8)  |
             arr[..., 2])

def from24(c):
    """Convierte entero 0xRRGGBB → RGB."""
    R = (c >> 16) & 0xFF
    G = (c >> 8)  & 0xFF
    B = c & 0xFF
    return np.stack([R, G, B], axis=-1).astype(np.uint8)

# ------------------------------
# Funciones auxiliares
# ------------------------------

def comb_xor(c1, c2):
    return from24((c1 ^ c2) & 0xFFFFFF)

def comb_or(c1, c2):
    return from24((c1 | c2) & 0xFFFFFF)

def comb_and(c1, c2):
    return from24((c1 & c2) & 0xFFFFFF)

def comb_add(c1, c2):
    return from24((c1 + c2) & 0xFFFFFF)

def comb_add_sep(c1, c2):
    R = (((c1 >> 16) & 0xFF) + ((c2 >> 16) & 0xFF)) & 0xFF
    G = (((c1 >> 8)  & 0xFF) + ((c2 >> 8)  & 0xFF)) & 0xFF
    B = ((c1 & 0xFF) + (c2 & 0xFF)) & 0xFF
    return from24((R << 16) | (G << 8) | B)

def comb_sub(c1, c2):
    return from24((c1 - c2) & 0xFFFFFF)

def comb_sub_sep(c1, c2):
    R = (((c1 >> 16) & 0xFF) - ((c2 >> 16) & 0xFF)) & 0xFF
    G = (((c1 >> 8)  & 0xFF) - ((c2 >> 8)  & 0xFF)) & 0xFF
    B = ((c1 & 0xFF) - (c2 & 0xFF)) & 0xFF
    return from24((R << 16) | (G << 8) | B)

def comb_mul(c1, c2):
    """MUL EXACTO StegSolve"""
    return from24((c1 * c2) & 0xFFFFFF)

def comb_mul_sep(c1, c2):
    R = (((c1 >> 16) & 0xFF) * ((c2 >> 16) & 0xFF)) & 0xFF
    G = (((c1 >> 8)  & 0xFF) * ((c2 >> 8)  & 0xFF)) & 0xFF
    B = ((c1 & 0xFF) * (c2 & 0xFF)) & 0xFF
    return from24((R << 16) | (G << 8) | B)

def comb_lightest(c1, c2):
    """Máximo por canal"""
    R = np.maximum((c1 >> 16) & 0xFF, (c2 >> 16) & 0xFF)
    G = np.maximum((c1 >> 8)  & 0xFF, (c2 >> 8)  & 0xFF)
    B = np.maximum(c1 & 0xFF, c2 & 0xFF)
    return from24((R << 16) | (G << 8) | B)

def comb_darkest(c1, c2):
    """Mínimo por canal"""
    R = np.minimum((c1 >> 16) & 0xFF, (c2 >> 16) & 0xFF)
    G = np.minimum((c1 >> 8)  & 0xFF, (c2 >> 8)  & 0xFF)
    B = np.minimum(c1 & 0xFF, c2 & 0xFF)
    return from24((R << 16) | (G << 8) | B)

# Lista de transformaciones
TRANSFORMS = {
    "xor": comb_xor,
    "or": comb_or,
    "and": comb_and,
    "add": comb_add,
    "add_sep": comb_add_sep,
    "sub": comb_sub,
    "sub_sep": comb_sub_sep,
    "mul": comb_mul,
    "mul_sep": comb_mul_sep,
    "lightest": comb_lightest,
    "darkest": comb_darkest,
}

# =========================================================
# GENERACIÓN DE TODAS LAS COMBINACIONES
# =========================================================

def generate_all(imA, imB, labelA, labelB):
    print(f"Generando combinaciones: {labelA} vs {labelB}")

    c1 = to24(imA)
    c2 = to24(imB)

    for name, fun in TRANSFORMS.items():
        out = fun(c1, c2)
        save_rgb(out, f"{labelA}__{labelB}__{name}.png")

    print(f"{labelA}-{labelB} completado.")

# =========================================================
# MAIN
# =========================================================

ensure_output()

print("Cargando imágenes v1.png y v2.png...")
im1 = load_rgb("v1.png")
im2 = load_rgb("v2.png")

print("Generando invertidas estilo StegSolve...")
im1_x = invert_xor(im1)
im2_x = invert_xor(im2)

save_rgb(im1_x, "v1_xored.png")
save_rgb(im2_x, "v2_xored.png")

# Generar las 52 combinaciones:
generate_all(im1,   im2,   "v1",   "v2")
generate_all(im1_x, im2,   "v1x",  "v2")
generate_all(im1,   im2_x, "v1",   "v2x")
generate_all(im1_x, im2_x, "v1x",  "v2x")

print("\nResultados en carpeta ./output/")

A continuación os muestro parte de las imágenes generadas por el script. El secreto oculto era un código QR que nos da la solución al reto.

Estamos ante un ELF un poco más interesante que los vistos anteriormente. Básicamente porque es divertido y fácil encontrar la solución en el decompilado y quizá por evocar recuerdos de tiempos pretéritos.

ELF Decompilado

/* This file was generated by the Hex-Rays decompiler version 8.4.0.240320.
   Copyright (c) 2007-2021 Hex-Rays <info@hex-rays.com>

   Detected compiler: GNU C++
*/

#include <defs.h>


//-------------------------------------------------------------------------
// Function declarations

__int64 (**init_proc())(void);
__int64 sub_400650(); // weak
// int printf(const char *format, ...);
// int puts(const char *s);
// void __noreturn exit(int status);
// size_t strlen(const char *s);
// __int64 __fastcall MD5(_QWORD, _QWORD, _QWORD); weak
// int sprintf(char *s, const char *format, ...);
// __int64 ptrace(enum __ptrace_request request, ...);
// __int64 strtol(const char *nptr, char **endptr, int base);
// __int64 __isoc99_scanf(const char *, ...); weak
// int memcmp(const void *s1, const void *s2, size_t n);
void __fastcall __noreturn start(__int64 a1, __int64 a2, void (*a3)(void));
void *sub_400730();
__int64 sub_400760();
void *sub_4007A0();
__int64 sub_4007D0();
void fini(void); // idb
void term_proc();
// int __fastcall _libc_start_main(int (__fastcall *main)(int, char **, char **), int argc, char **ubp_av, void (*init)(void), void (*fini)(void), void (*rtld_fini)(void), void *stack_end);
// __int64 _gmon_start__(void); weak

//-------------------------------------------------------------------------
// Data declarations

_UNKNOWN main;
_UNKNOWN init;
__int64 (__fastcall *funcs_400E29)() = &sub_4007D0; // weak
__int64 (__fastcall *off_601DF8)() = &sub_4007A0; // weak
__int64 (*qword_602010)(void) = NULL; // weak
char *off_602080 = "FLAG-%s\n"; // idb
char a7yq2hryrn5yJga[16] = "7Yq2hrYRn5Y`jga"; // weak
const char aO6uH[] = "(O6U,H\""; // idb
_UNKNOWN unk_6020B8; // weak
_UNKNOWN unk_6020C8; // weak
char byte_6020E0; // weak
char s1; // idb
char byte_602110[]; // weak
char byte_602120[33]; // weak
char byte_602141[7]; // idb
__int64 qword_602148; // weak
__int64 qword_602150; // weak
__int64 qword_602158; // weak
__int64 qword_602160; // weak
__int64 qword_602178; // weak


//----- (0000000000400630) ----------------------------------------------------
__int64 (**init_proc())(void)
{
  __int64 (**result)(void); // rax

  result = &_gmon_start__;
  if ( &_gmon_start__ )
    return (__int64 (**)(void))_gmon_start__();
  return result;
}
// 6021D8: using guessed type __int64 _gmon_start__(void);

//----- (0000000000400650) ----------------------------------------------------
__int64 sub_400650()
{
  return qword_602010();
}
// 400650: using guessed type __int64 sub_400650();
// 602010: using guessed type __int64 (*qword_602010)(void);

//----- (0000000000400700) ----------------------------------------------------
// positive sp value has been detected, the output may be wrong!
void __fastcall __noreturn start(__int64 a1, __int64 a2, void (*a3)(void))
{
  __int64 v3; // rax
  int v4; // esi
  __int64 v5; // [rsp-8h] [rbp-8h] BYREF
  char *retaddr; // [rsp+0h] [rbp+0h] BYREF

  v4 = v5;
  v5 = v3;
  _libc_start_main((int (__fastcall *)(int, char **, char **))main, v4, &retaddr, (void (*)(void))init, fini, a3, &v5);
  __halt();
}
// 400706: positive sp value 8 has been found
// 40070D: variable 'v3' is possibly undefined

//----- (0000000000400730) ----------------------------------------------------
void *sub_400730()
{
  return &unk_6020C8;
}

//----- (0000000000400760) ----------------------------------------------------
__int64 sub_400760()
{
  return 0LL;
}

//----- (00000000004007A0) ----------------------------------------------------
void *sub_4007A0()
{
  void *result; // rax

  if ( !byte_6020E0 )
  {
    result = sub_400730();
    byte_6020E0 = 1;
  }
  return result;
}
// 6020E0: using guessed type char byte_6020E0;

//----- (00000000004007D0) ----------------------------------------------------
__int64 sub_4007D0()
{
  return sub_400760();
}

//----- (00000000004007D7) ----------------------------------------------------
__int64 __fastcall main(int a1, char **a2, char **a3)
{
  size_t v3; // rax
  size_t v4; // rax
  int i; // [rsp+1Ch] [rbp-24h]
  int n; // [rsp+20h] [rbp-20h]
  int m; // [rsp+24h] [rbp-1Ch]
  int k; // [rsp+28h] [rbp-18h]
  int j; // [rsp+2Ch] [rbp-14h]

  if ( ptrace(PTRACE_TRACEME, 0LL, 0LL, 0LL) == -1 )
    goto LABEL_2;
  if ( a1 > 4 )
  {
    qword_602148 = strtol(a2[1], 0LL, 10);
    if ( qword_602148 )
    {
      qword_602150 = strtol(a2[2], 0LL, 10);
      if ( qword_602150 )
      {
        qword_602158 = strtol(a2[3], 0LL, 10);
        if ( qword_602158 )
        {
          qword_602160 = strtol(a2[4], 0LL, 10);
          if ( qword_602160 )
          {
            if ( -24 * qword_602148 - 18 * qword_602150 - 15 * qword_602158 - 12 * qword_602160 == -18393
              && 9 * qword_602158 + 18 * (qword_602150 + qword_602148) - 9 * qword_602160 == 4419
              && 4 * qword_602158 + 16 * qword_602148 + 12 * qword_602150 + 2 * qword_602160 == 7300
              && -6 * (qword_602150 + qword_602148) - 3 * qword_602158 - 11 * qword_602160 == -8613 )
            {
              qword_602178 = qword_602158 + qword_602150 * qword_602148 - qword_602160;
              sprintf(byte_602141, "%06x", qword_602178);
              v4 = strlen(byte_602141);
              MD5(byte_602141, v4, byte_602110);
              for ( i = 0; i <= 15; ++i )
                sprintf(&byte_602120[2 * i], "%02x", (unsigned __int8)byte_602110[i]);
              printf(off_602080, byte_602120);
              exit(0);
            }
          }
        }
      }
    }
LABEL_2:
    printf("password : ");
    __isoc99_scanf("%s", &s1);
    if ( strlen(&s1) > 0x10 )
    {
      puts("the password must be less than 16 character");
      exit(1);
    }
    for ( j = 0; j < strlen(&s1); ++j )
      *(&s1 + j) ^= 6u;
    if ( !strcmp(&s1, a7yq2hryrn5yJga) )
    {
      v3 = strlen(&s1);
      MD5(&s1, v3, byte_602110);
      for ( k = 0; k <= 15; ++k )
        sprintf(&byte_602120[2 * k], "%02x", (unsigned __int8)byte_602110[k]);
      printf(off_602080, byte_602120);
      exit(0);
    }
    puts("bad password!");
    exit(0);
  }
  printf("password : ");
  __isoc99_scanf("%s", &s1);
  if ( strlen(&s1) > 0x10 )
  {
    puts("the password must be less than 16 character");
    exit(1);
  }
  for ( m = 0; m < strlen(&s1); ++m )
  {
    *(&s1 + m) ^= 2u;
    ++*(&s1 + m);
    *(&s1 + m) = ~*(&s1 + m);
  }
  if ( !memcmp(&s1, &unk_6020B8, 9uLL) )
  {
    for ( n = 0; n < strlen(aO6uH); n += 2 )
    {
      aO6uH[n] ^= 0x45u;
      aO6uH[n + 1] ^= 0x26u;
    }
    puts(aO6uH);
  }
  else
  {
    puts("bad password!");
  }
  return 0LL;
}
// 4006A0: using guessed type __int64 __fastcall MD5(_QWORD, _QWORD, _QWORD);
// 4006E0: using guessed type __int64 __isoc99_scanf(const char *, ...);
// 602148: using guessed type __int64 qword_602148;
// 602150: using guessed type __int64 qword_602150;
// 602158: using guessed type __int64 qword_602158;
// 602160: using guessed type __int64 qword_602160;
// 602178: using guessed type __int64 qword_602178;

//----- (0000000000400DE0) ----------------------------------------------------
void __fastcall init(unsigned int a1, __int64 a2, __int64 a3)
{
  signed __int64 v3; // rbp
  __int64 i; // rbx

  v3 = &off_601DF8 - &funcs_400E29;
  init_proc();
  if ( v3 )
  {
    for ( i = 0LL; i != v3; ++i )
      (*(&funcs_400E29 + i))();
  }
}
// 601DF0: using guessed type __int64 (__fastcall *funcs_400E29)();
// 601DF8: using guessed type __int64 (__fastcall *off_601DF8)();

//----- (0000000000400E54) ----------------------------------------------------
void term_proc()
{
  ;
}

// nfuncs=33 queued=10 decompiled=10 lumina nreq=0 worse=0 better=0
// ALL OK, 10 function(s) have been successfully decompiled

Análisis estático

Anti-debug

Si la función ptrace retorna -1, significa que el programa está siendo depurado y redirige a LABEL_2.

if (ptrace(PTRACE_TRACEME, 0LL, 0LL, 0LL) == -1) {
    goto LABEL_2;
}

Cálculos y validaciones

El programa espera al menos 5 argumentos (nombre del programa y cuatro números enteros). Si se proporcionan los cuatro números enteros, se realizan los siguientes cálculos:

if (-24 * qword_602148 - 18 * qword_602150 - 15 * qword_602158 - 12 * qword_602160 == -18393
    && 9 * qword_602158 + 18 * (qword_602150 + qword_602148) - 9 * qword_602160 == 4419
    && 4 * qword_602158 + 16 * qword_602148 + 12 * qword_602150 + 2 * qword_602160 == 7300
    && -6 * (qword_602150 + qword_602148) - 3 * qword_602158 - 11 * qword_602160 == -8613)

Esto es un sistema de ecuaciones lineales mondo y lirondo que debe ser resuelto para encontrar los valores correctos de qword_602148, qword_602150, qword_602158 y qword_602160. Una vez resuelto el sistema de ecuaciones se realiza la operación:

 qword_602178 = qword_602158 + qword_602150 * qword_602148 - qword_602160;

A continuación se pasa el resultado de la variable qword_602178 a hexadecimal y se genera su hash MD5.

Solución en Python

Lo más rápido en esta ocasión es usar Python, pero esto se puede resolver hasta con lápiz y papel 😉

from sympy import symbols, Eq, solve
import hashlib

# Definir las variables
A, B, C, D = symbols('A B C D')

# Definir las ecuaciones
eq1 = Eq(-24*A - 18*B - 15*C - 12*D, -18393)
eq2 = Eq(9*C + 18*(A + B) - 9*D, 4419)
eq3 = Eq(4*C + 16*A + 12*B + 2*D, 7300)
eq4 = Eq(-6*(A + B) - 3*C - 11*D, -8613)

# Resolver el sistema de ecuaciones
solution = solve((eq1, eq2, eq3, eq4), (A, B, C, D))

# Verificar si se encontró una solución
if solution:
    print("Solución encontrada:")
    print(solution)

    # Obtener los valores de A, B, C y D
    A_val = solution[A]
    B_val = solution[B]
    C_val = solution[C]
    D_val = solution[D]

    # Mostrar los valores encontrados
    print(f"A = {A_val}")
    print(f"B = {B_val}")
    print(f"C = {C_val}")
    print(f"D = {D_val}")

    # Calcular qword_602178
    qword_602178 = C_val + B_val * A_val - D_val
    qword_602178 = int(qword_602178)  # Convertir a entero de Python
    print(f"qword_602178 = {qword_602178}")

    # Convertir qword_602178 a una cadena en formato hexadecimal
    byte_602141 = f"{qword_602178:06x}"
    print(f"byte_602141 (hex) = {byte_602141}")

    # Calcular el MD5 de la cadena
    md5_hash = hashlib.md5(byte_602141.encode()).hexdigest()
    print(f"MD5 hash = {md5_hash}")

    # Generar la flag
    flag = f"FLAG-{md5_hash}"
    print(f"Flag = {flag}")

else:
    print("No se encontró una solución.")

Al ejecutar el script veremos algo como esto:

Solución encontrada:
{A: 227, B: 115, C: 317, D: 510}
A = 227
B = 115
C = 317
D = 510
qword_602178 = 25912
byte_602141 (hex) = 006538
MD5 hash = 21a84f2c7c7fd432edf1686215db....
Flag = FLAG-21a84f2c7c7fd432edf1686215db....

Warning: This challenge is still active and therefore should not be resolved using this information.
Aviso: Este reto sigue en activo y por lo tanto no se debería resolver utilizando esta información.

Introducción

Realistic Challenge 4: There is a site offering protection against hackers to website owners, the service is far too overpriced and the people running the service don’t know anything about security. Look around their site, and see how protected it is.

Hay un sitio que ofrece protección contra los hackers. El servicio tiene un precio abusivo, echa un vistazo a la web y evalúa su pretección.

Analizando a la víctima

Vemos un escueto menú pero con cosas interesantes.

Pinchamos sobre «Testimonials» y a continuación en «Customer 1»

Vemos que hay solo 3 «customers», vamos a introducir manualmente un 5 haber que pasa.

Ok, nos genera el siguiente error.

Probamos ahora con un enlace interno que nos genera el siguiente error.

http://www.thisislegal.com/newr/src/read.php?customer=../orders.php

Nos llama la atención «../beqref.cuc«. Parece una encriptación simple, probemos a poner eso mismo en el navegador.

http://www.thisislegal.com/newr/src/read.php?customer=../beqref.cuc

 

Nuestras sospechas son acertadas, ahora el error muestra esto.

Explotando a la víctima

Probamos varias cosas y al final conseguimos algo relevante con «order2.php«.

http://www.thisislegal.com/newr/src/read.php?customer=../beqre2.cuc
Tenemos un directorio interesante «secure«, si entramos en el nos salta un Login típico protegido con «.htaccess«. Lo lógico a continuación es hacernos con el archivo «.htpasswd«
http://www.thisislegal.com/newr/src/read.php?customer=../frpher/.ugcnffjq

 

Una vez obtenido el contenido del archivo «.htpasswd» lo siguiente es crackear el password con John the Ripper. Nos logueamos en la carpeta secure y reto superado.

Links

En este reto se nos entrega un archivo WAV de 9,92 MB. Tras escucharlo y analizarlo por encima con Audacity no llego a ningún lado por lo que me tiro al descarte de herramientas conocidas, y en ésta ocasión sale a escena DeepSound.

Sin más dilación extraemos el JPG y continuamos.

La aparición en escena de DeepSound me hace sospechar sobre el uso de herramientas conocidas y ¡bingo!, sale a escena StegHide. En esta ocasión el autor del reto nos lo ha puesto fácil y la extracción no requiere clave.

Al abrir el archivo TXT como texto vemos lo siguiente:

y si lo abrimos con un editor hexadecimal vemos esto otro:

Claramente el archivo esconde algo que por la repetición de los caracteres me hace sospechar de un simple XOR y efectivamente la flag está XOReada. Tras un ataque preliminar, digamos que los árboles no me dejaban ver el bosque, de modo que limpié los bytes correspondientes a la frase «this 󠁓󠁈󠁓󠁻󠁴is 󠀰󠀰󠁟󠀳󠀴the 󠁳󠁹󠁟󠁭󠀴flag 󠁮󠁽󠀠:)» y procesé de nuevo obteniendo por fin la ansiada flag.

RAW bytes
FF FE 74 00 68 00 69 00 73 00 20 00 40 DB 53 DC 40 DB 48 DC 40 DB 53 DC 40 DB 7B DC 40 DB 74 DC 69 00 73 00 20 00 40 DB 30 DC 40 DB 30 DC 40 DB 5F DC 40 DB 33 DC 40 DB 34 DC 74 00 68 00 65 00 20 00 40 DB 73 DC 40 DB 79 DC 40 DB 5F DC 40 DB 6D DC 40 DB 34 DC 66 00 6C 00 61 00 67 00 20 00 40 DB 6E DC 40 DB 7D DC 40 DB 20 DC 3A 00 29 00

Cleaned bytes [quitando this 󠁓󠁈󠁓󠁻󠁴is 󠀰󠀰󠁟󠀳󠀴the 󠁳󠁹󠁟󠁭󠀴flag 󠁮󠁽󠀠:)]
FF FE 40 DB 53 DC 40 DB 48 DC 40 DB 53 DC 40 DB 7B DC 40 DB 74 DC 40 DB 30 DC 40 DB 30 DC 40 DB 5F DC 40 DB 33 DC 40 DB 34 DC 40 DB 73 DC 40 DB 79 DC 40 DB 5F DC 40 DB 6D DC 40 DB 34 DC 40 DB 6E DC 40 DB 7D DC 40 DB 20 DC


clave XOR == 00fc60fb

Resultado
S   H   S   {   t   0   0   _   3   4   s   y   _   m   4   n   }