Usa el árbol para saltar entre colecciones sin salir del lector.

archivo Seleccionar un writeup Abrir árbol
HackTheBox/Challenges/Challenge Noradar.md READ_ONLY

Challenge NoRadar

Este reto consiste en analizar estáticamente el binario de un juego para aplicar ingeniería inversa a la lógica de una entidad oculta y la solución consiste en extraer coordenadas de memoria mediante un script para generar una imagen que revela la flag.

Una revisión inicial del juego muestra un mapa ambientado en el espacio con cubos que interactúan. Como la partida habitual no parece revelar ninguna solución aparente, es necesario inspeccionar más a fondo el ejecutable subyacente. El comando file demuestra que el ejecutable es un binario ELF de 64 bits sin despojar (not stripped), lo que significa que los nombres originales de las funciones están intactos, facilitando enormemente el análisis.

$ file noradar
noradar: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=6feab769773bca287ae8231e8fb0d9920d6529b5, for GNU/Linux 3.2.0, not stripped

Al volcar los símbolos con nm, se observan los bucles principales del juego y otros subsistemas. Destaca la presencia de tres tipos distintos de cubos. Dado que en el juego solo hay dos tipos que interactúan visiblemente, es altamente probable que el tercer tipo contenga un comportamiento especial u oculto. También se identifican variables interesantes como waypoint_index.

$ nm noradar
00000000000024a0 T animated_load
0000000000002480 T animated_reset
00000000000023c0 T animated_update
...
0000000000001e70 T green_cube1_new
00000000000019d0 T green_cube1_update
0000000000001f50 T green_cube2_new
0000000000002010 T green_cube3_new
0000000000001ab0 T green_cube3_update
...
0000000000008684 b waypoint_index.100
0000000000004050 T window_close
0000000000004080 T window_init

A través de un análisis estático más profundo con Cutter, se examina el código fuente decompilado. La función load_assets se encarga de analizar el archivo de recursos assets.dmp.

bool load_assets(int64_t arg1)
{
    int32_t iVar1;
    FILE *stream;
    int64_t iVar2;
    uint32_t uVar3;
    uint64_t uVar4;
    bool bVar5;
    int64_t var_2ch;

    bVar5 = false;
    stream = (FILE *)fopen(arg1, data.00005004);
    if (stream == (FILE *)0x0) {
        bVar5 = true;
        dprintf(2, "can\'t open %s\n", arg1);
    } else {
code_r0x000017f0:
        while ((iVar2 = fread(&var_2ch, 4, 1, stream), iVar2 != 0 && (bVar5 == false))) {
            if ((uint32_t)var_2ch == 3) {
                iVar1 = load_assets_texture(0x81f0, stream);
                bVar5 = iVar1 != 0;
            } else {
                if (3 < (uint32_t)var_2ch) goto code_r0x000018b0;
                if ((uint32_t)var_2ch == 1) {
                    fread(data.00008190, 0x10, 1, stream);
                } else if ((uint32_t)var_2ch == 2) {
                    fread(game, 4, 1, stream);
                    fread(data.00008184, 4, 1, stream);
                    iVar1 = _game * _data.00008184 * 3;
                    _data.00008188 = malloc(iVar1);
                    if (_data.00008188 == 0) goto code_r0x00001937;
                    fread(_data.00008188, iVar1, 1, stream);
                }
            }
        }
        fclose(stream);
    }
    return bVar5;
code_r0x000018b0:
    if ((uint32_t)var_2ch == 4) {
        fread(data.00008258, 4, 1, stream);
        uVar3 = _data.00008258;
        _data.00008250 = malloc((uint64_t)_data.00008258 * 0x50);
        if (_data.00008250 == 0) {
code_r0x00001937:
            bVar5 = true;
        } else {
            uVar4 = 0;
            if (uVar3 != 0) {
                do {
                    iVar1 = load_assets_texture(_data.00008250 + uVar4 * 0x50, stream);
                    if (iVar1 != 0) goto code_r0x00001937;
                    uVar3 = (int32_t)uVar4 + 1;
                    uVar4 = (uint64_t)uVar3;
                } while (uVar3 < _data.00008258);
            }
        }
    }
    goto code_r0x000017f0;
}

Al revisar la función green_cube3_update, se descubre la lógica que rige a este tercer tipo de cubo. La rutina itera a través de una lista estática de 187 coordenadas ubicadas en la dirección de memoria 0x5060.

void green_cube3_update(int64_t arg1)
{
    uint32_t uVar1;
    int64_t iVar2;
    undefined8 *puVar3;
    int64_t *piVar4;
    double dVar5;
    double dVar6;
    double dVar7;
    double dVar8;
    int64_t var_bb8h;
    int64_t var_bb0h;

    uVar1 = _waypoint_index.1;
    puVar3 = (undefined8 *)data.00005060;
    piVar4 = &var_bb8h;
    for (iVar2 = 0x176; iVar2 != 0; iVar2 = iVar2 + -1) {
        *piVar4 = *puVar3;
        puVar3 = puVar3 + 1;
        piVar4 = piVar4 + 1;
    }
    dVar6 = (double)(&var_bb0h)[(int64_t)(int32_t)_waypoint_index.1 * 2];
    if ((dVar6 == 0.0) && ((double)(&var_bb8h)[(int64_t)(int32_t)_waypoint_index.1 * 2] == 0.0)) {
        _waypoint_index.1 = _waypoint_index.1 + 1;
        _offset_x.0 = _offset_x.0 + 0xe;
        *(undefined8 *)(arg1 + 0x28) = 0;
        *(undefined8 *)(arg1 + 0x30) = 0;
        return;
    }
    dVar8 = *(double *)(arg1 + 0x28);
    dVar7 = *(double *)(arg1 + 0x30);
    dVar5 = (double)_offset_x.0 + (double)(&var_bb8h)[(int64_t)(int32_t)_waypoint_index.1 * 2];
    if ((dVar8 == 0.0) && (dVar7 == 0.0)) {
        *(double *)(arg1 + 0x28) = dVar5;
        *(double *)(arg1 + 0x30) = dVar6;
    } else {
        dVar5 = dVar5 + 0.5;
        dVar8 = (dVar5 - dVar8) * 192.0 * (double)_deltatime + dVar8;
        dVar7 = ((dVar6 + 0.5) - dVar7) * 192.0 * (double)_deltatime + dVar7;
        dVar5 = dVar5 - dVar8;
        *(double *)(arg1 + 0x28) = dVar8;
        dVar6 = (dVar6 + 0.5) - dVar7;
        *(double *)(arg1 + 0x30) = dVar7;
        dVar6 = dVar5 * dVar5 + dVar6 * dVar6;
        if (dVar6 < 0.0) {
            dVar6 = (double)sqrt();
        } else {
            dVar6 = SQRT(dVar6);
        }
        if (dVar6 < 0.1) {
            if (0xbc < uVar1) {
                _waypoint_index.1 = 0;
                _offset_x.0 = 0;
                return;
            }
            _waypoint_index.1 = uVar1 + 1;
            return;
        }
    }
    return;
}

La clave para visualizar la flag se encuentra en trazar la ruta de este cubo oculto. Sin embargo, al observar de cerca las condiciones en la rutina, existe un manejo específico para los puntos donde ambas coordenadas son 0.0. En tales casos, se incrementa la variable offset_x en 14 (0xe).

if ((dVar6 == 0.0) && ((double)(&var_bb8h)[(int64_t)(int32_t)_waypoint_index.1 * 2] == 0.0)) {
        _waypoint_index.1 = _waypoint_index.1 + 1;
        _offset_x.0 = _offset_x.0 + 0xe;

Esto indica que las coordenadas (0.0, 0.0) actúan como un mecanismo de separación para cada uno de los caracteres de la flag, desplazando el punto de dibujo para que las letras no se superpongan entre sí. Para aprovechar esta lógica de espaciado, se construye un script de Python que lee el archivo binario, extrae los pares de coordenadas en formato de doble precisión (double) a partir del offset 0x5060, y procesa estos puntos para generar un archivo SVG estructurado.

$ cat exploit/exploit.py
import struct

with open('gamepwn_noradar/noradar', 'rb') as f:
    f.seek(0x5060)
    data = f.read(187 * 16)

pts = [struct.unpack('dd', data[i:i+16]) for i in range(0, len(data), 16)]

with open('flag.svg', 'w') as f:
    f.write('<svg width="15000" height="300" '
        'xmlns="http://www.w3.org/2000/svg">\n')

    offset_x = 0
    path = []

    for x, y in pts:
        if x == 0.0 and y == 0.0:
            offset_x += 14
            if path:
                f.write(
                    f'  <polyline points="{" ".join(path)}" '
                    f'fill="none" stroke="black" stroke-width="4" />\n'
            )
                path = []
        else:
            path.append(f"{(x + offset_x) * 20},{y * 20}")

    if path:
        f.write(
            f'  <polyline points="{" ".join(path)}" '
            f'fill="none" stroke="black" stroke-width="4" />\n'
    )

    f.write('</svg>\n')

Al ejecutar el script, las coordenadas se interpretan correctamente y se plasma el trazado vectorial.

$ python3 exploit/exploit.py
$ ls
452?auth_user_id=453332&expires=1782314119&signature=fbe664b936b23287e5b190c4ddc5c66146b0b69ade96a83c5c7773f37967dbdb  LINK.lst  OTHERS.lst  gamepwn_noradar  exploit  exploit.feature  flag.svg  exploit.yml

Al abrir la imagen resultante, el espaciado aplicado correctamente revela de manera legible el texto oculto y se obtiene la flag exitosamente.

HTB{FLAG}