Solution de U03 pour Fifty Shades of White (Pinkman)

reverse linux x86/x64 keygenme

23 janvier 2025

Cette épreuve est la suite de l’épreuve Fifty Shades of White (Junior), nous ne reprenons donc pas le détail des points vus précédemment.

Nous analysons le code de la fonction

// param_1: Contient le nom d'utilisateur
// param_2: contient le numéro de série de la licence
//
uint validate(char *param_1,char *param_2) {
  size_t sVar1;
  void *local_38;
  undefined local_2c [4];
  uint local_28;
  uint local_24;
  ulong local_20;
  int local_14;
  int local_10;
  uint local_c;

  sVar1 = strlen(param_2);
  sha256(param_2,sVar1,&local_38,local_2c);
  local_c = 1;
  for (local_10 = 0; local_10 < 3; local_10 = local_10 + 1) {
    local_14 = 0;
    local_20 = (ulong)local_10;
    while( true ) {
      sVar1 = strlen(param_1);
      if (sVar1 <= local_20) break;
      local_14 = local_14 + param_1[local_20];
      local_20 = local_20 + 3;
    }
    local_24 = (local_14 * 0x13 + 0x37) % 0x7f;
    local_28 = ((uint)*(byte *)((long)local_10 + (long)local_38) * 0x37 + 0x13) % 0x7f;
    local_c = local_c & local_24 == local_28;
  }
  CRYPTO_free(local_38);
  return local_c;
}

Un hash sha256 du numéro de licence est généré, ensuite des calculs sont effectués avec certains caractères du nom d’utilisateur et certains caractères du hash du numéro de licence. A chaque fois le résultat de ces deux calculs est comparé, 3 rondes de calculs sont effectuées. La licence est déclarée valide si sur chacune des 3 rondes le résultat est correct.

Les fonctions de hashage ne sont pas réversibles, et donc il n’est pas possible de calculer un numéro de licence valide, il est nécessaire tirer des numéros de licence aléatoires et de faire un appel à la fonction validate jusqu’à ce qu’on trouve un numéro de licence correct pour le nom d’utilisateur.

Il y a 3 rondes au cours desquelles on compare 2 octets entre eux, on peut estimer que pour chaque clé aléatoire on a une chance sur 2^24 que la licence soit valide (1 sur 16777216).

On écrit une fonction validate en Python:

#!/usr/bin/python3

from Cryptodome.Hash import SHA256

def validate(NAME, SERIAL):

  h = SHA256.new()
  h.update(SERIAL)
  HASH = h.digest()

  local_c = True

  for local_10 in range(0,3):
      local_14 = 0
      local_20 = local_10
      while True:
        sVar1 = len(NAME)
        if (sVar1 <= local_20):
          break
        local_14 = local_14 + NAME[local_20];
        local_20 = local_20 + 3
      local_24 = (local_14 * 0x13 + 0x37) % 0x7f;
      local_28 = (HASH[local_10] * 0x37 + 0x13) % 0x7f;
      local_c = local_c & (local_24 == local_28);

  return local_c

print(validate(b'Walter White Junior', b'1d117c5a-297d-4ce6-9186-d4b84fb7f230'))
print(validate(b'Walter White Junior', b'00000000-297d-4ce6-9186-d4b84fb7f230'))

Un numéro de licence est composé de 36 caractères, des caractères hexadécimaux et des tirets.

nom = b"Walter White Junior"
cpt = 0
while True:
  cpt = cpt + 1
  licence = ('-'.join([urandom(4).hex(), urandom(2).hex(), urandom(2).hex(), urandom(2).hex(), urandom(6).hex()])).encode(encoding="ascii")
  if validate(nom, licence) == True:
    break
print(f"cpt: {cpt} / {nom} / {licence}")

Nous essayons plusieurs fois de trouver un numéro de licence pour Junior, il faut entre 1.5 millions et 7.7 millions de tentatives pour trouver un numéro valide, ce qui prend entre 28 secondes et 2m19s.

$ time python3 brute.py
cpt: 5276080 / b'Walter White Junior' / b'f5fba6a2-de07-edc3-3c60-b6196ab3f093'
real   1m35,326s

$ time python3 brute.py
cpt: 7683950 / b'Walter White Junior' / b'08a1244d-97a5-721b-2f71-2aa22e35e571'
real   2m19,854s

$ time python3 brute.py
cpt: 1506981 / b'Walter White Junior' / b'b5dc2030-2d87-3b07-0487-f37cfc2cef98'
real   0m28,183s

$ time python3 brute.py
cpt: 4712341 / b'Walter White Junior' / b'9a88386c-0cbc-d834-69b1-73a210a289db'
real   1m24,783s

On note que c’est long et nous allons en avoir au moins 50 à calculer. Pour optimiser la durée d’exécution il faudrait multithreader la partie force brute pour occuper chaque core du processeur à 100%.

Nous disposons maintenant de la possibilité de calculer les licences qui nous sont demandées.

#!/usr/bin/python3

from Cryptodome.Hash import SHA256
from os import urandom
import textwrap
import base64
from pwn import *
import re

def validate(NAME, SERIAL):

    h = SHA256.new()
    h.update(SERIAL)
    HASH = h.digest()

    local_c = True

    for local_10 in range(0, 3):
        local_14 = 0
        local_20 = local_10
        while True:
            sVar1 = len(NAME)
            if (sVar1 <= local_20):
                break
            local_14 = local_14 + NAME[local_20]
            local_20 = local_20 + 3
        local_24 = (local_14 * 0x13 + 0x37) % 0x7f
        local_28 = (HASH[local_10] * 0x37 + 0x13) % 0x7f
        local_c = local_c & (local_24 == local_28)
        # print(f"local_c: {local_c} / local_24: {local_24} / local_28: {local_28}")

    return local_c


conn = remote('localhost', 4000)

while True:

    while True:
        x = conn.recvline()
        print(x)
        if re.search(b"FCSC\{", x):
            print(f"FLAG: {x}")

        if r := re.search(b"Give me a valid(.*)?license for username: (.*)", x):
            if r.group(1) == b' admin ':
                type_licence = 1337
            else:
                type_licence = 1
            nom_licence = r.group(2)
            break

    print(f"Licence Type : {type_licence}")
    print(f"Licence Nom  : {nom_licence}")

    while True:
        numero_licence = ('-'.join([urandom(4).hex(), urandom(2).hex(), urandom(2).hex(), urandom(2).hex(), urandom(6).hex()])).encode(encoding="ascii")
        if validate(nom_licence, numero_licence) == True:
            break

    print(f"{nom_licence} / {numero_licence}")

    licence = f"Name: {nom_licence.decode('ascii')}\nSerial: {numero_licence.decode('ascii')}\nType: {type_licence}\n"
    print(licence)
    licence_base64 = '\n'.join(textwrap.wrap(base64.b64encode(licence.encode("ascii")).decode('ascii'), width=27))

    EXPLOIT = f"----BEGIN WHITE LICENSE----\n{licence_base64}\n-----END WHITE LICENSE-----\n"
    print(EXPLOIT)
    conn.send(EXPLOIT.encode("ascii"))
    conn.send(b'\n')

Le script suivant permet de synchroniser les opérations :

#!/bin/bash

set -e

if [ ! -f docker-compose.yml  ]; then
    wget https://hackropole.fr/challenges/fcsc2024-reverse-fifty-shades-of-white/docker-compose.public.yml  -O docker-compose.yml
fi


docker-compose up -d

while ! nc -z localhost 4000; do sleep 1; done

python3 022_Fifty_shades_of_white_Pinkman.py

docker-compose down

Le résultat est le suivant:

022_fifty_shades_of_white_pinkman_fifty-shades-of-white_1 is up-to-date
[+] Opening connection to localhost on port 4000: Done
b'[*] Send empty lines to mark the end of your inputs.\n'
b'[*] Give me a valid admin license for username: Walter White Junior\n'
Licence Type : 1337
Licence Nom  : b'Walter White Junior'
b'Walter White Junior' / b'c86abddc-bfd0-7c51-7043-facdd0d70a7c'
Name: Walter White Junior
Serial: c86abddc-bfd0-7c51-7043-facdd0d70a7c
Type: 1337

----BEGIN WHITE LICENSE----
TmFtZTogV2FsdGVyIFdoaXRlIEp
1bmlvcgpTZXJpYWw6IGM4NmFiZG
RjLWJmZDAtN2M1MS03MDQzLWZhY
2RkMGQ3MGE3YwpUeXBlOiAxMzM3
Cg==
-----END WHITE LICENSE-----

b'Valid admin license for Walter White Junior!\n'
b'Well done! Here is the flag for the Junior challenge:\n'
b'FCSC{xxxxxxxx}\n'
FLAG: b'FCSC{xxxxxxxx}\n'
b'[*] Give me a valid license for username: Jesse Pinkman\n'
Licence Type : 1
Licence Nom  : b'Jesse Pinkman'
b'Jesse Pinkman' / b'e8306f31-07d4-170a-65e3-b6da4a512f46'
Name: Jesse Pinkman
Serial: e8306f31-07d4-170a-65e3-b6da4a512f46
Type: 1

   .../...

b'Valid license for jQEnub81f60Kk5Xnm!\n'
b'[*] Give me a valid license for username: 2f3UfQ7Qm7m6tkqDV7T9k1GY\n'
Licence Type : 1
Licence Nom  : b'2f3UfQ7Qm7m6tkqDV7T9k1GY'
b'2f3UfQ7Qm7m6tkqDV7T9k1GY' / b'd130c1e4-3e7a-01fe-92aa-34f709cfeba8'
Name: 2f3UfQ7Qm7m6tkqDV7T9k1GY
Serial: d130c1e4-3e7a-01fe-92aa-34f709cfeba8
Type: 1

----BEGIN WHITE LICENSE----
TmFtZTogMmYzVWZRN1FtN202dGt
xRFY3VDlrMUdZClNlcmlhbDogZD
EzMGMxZTQtM2U3YS0wMWZlLTkyY
WEtMzRmNzA5Y2ZlYmE4ClR5cGU6
IDEK
-----END WHITE LICENSE-----

b'Valid license for 2f3UfQ7Qm7m6tkqDV7T9k1GY!\n'
b'Well done! Here is the flag for the Pinkman challenge:\n'
b'FCSC{xxxxxxxx}\n'
FLAG: b'FCSC{xxxxxxxx}\n'