Un binaire étrange, une page de code obscur, qu'est ce qui pourrait mal se passer ?
L'objectif ici est de déchiffrer le binaire fourni avec le challenge et de renvoyer le flag. Pour cela, on va passer par plusieurs étapes.
L'idée du challenge est de reverse le code ASM donné et de comprendre les différents mécanismes de chiffrement appliqués, qui sont pour la plus part des xor sous différentes formes : un xor simple, un chiffrement de César, un autre xor simple mais obfusqué, et un xor qui prend sa partie du flag à l'envers et qui xor la fin de sa partie avec le début de la clé. La clé est soit prédéfinie, soit elle s'appuie sur le timestamp, qui est à bruteforce.
Script de déchiffrement du flag à partir du texte chiffré fourni : 1) On découpe l’hexadécimal en 4 blocs de 20 caractères (chaque bloc représente 10 octets chiffrés). 2) On applique l’inverse des 4 opérations décrites en assembleur : - Part1 : XOR avec xorK = 0x53 - Part2 : soustraction de polyK = 9 (mod 256) - Part3 : XOR avec optK = 113 - Part4 : XOR avec genK = 0x51 3) On réassemble le flag en intercalant les 4 sous-parties (P1, P2, P3, P4) pour reconstituer l’ordre original des 40 octets du flag. """ def hex_to_bytes(hexstr: str) -> bytes: """Convertit une chaîne hex en bytes.""" return bytes.fromhex(hexstr) def decrypt_part1(c1: bytes, xorK: int) -> bytes: """Inverse de : C1[i] = P1[i] XOR xorK ⟹ P1[i] = C1[i] XOR xorK.""" return bytes([b ^ xorK for b in c1]) def decrypt_part2(c2: bytes, polyK: int) -> bytes: """ Inverse de : C2[i] = (P2[i] + polyK) mod 256 ⟹ P2[i] = (C2[i] - polyK) mod 256 """ return bytes([(b - polyK) & 0xFF for b in c2]) def decrypt_part3(c3: bytes, optK: int) -> bytes: """Inverse de : C3[i] = P3[i] XOR optK ⟹ P3[i] = C3[i] XOR optK.""" return bytes([b ^ optK for b in c3]) def decrypt_part4(c4: bytes, genK: int) -> bytes: """Inverse de : C4[i] = P4[i] XOR genK ⟹ P4[i] = C4[i] XOR genK.""" return bytes([b ^ genK for b in c4]) def interleave_parts(p1: bytes, p2: bytes, p3: bytes, p4: bytes) -> bytes: """ Reconstitue le flag complet (40 octets) en intercalant les 4 sous-parties de 10 octets chacune : flag = [ p1[0], p2[0], p3[0], p4[0], p1[1], p2[1], p3[1], p4[1], ... p1[9], p2[9], p3[9], p4[9] ] """ flag = bytearray(40) for i in range(10): flag[4*i + 0] = p1[i] flag[4*i + 1] = p2[i] flag[4*i + 2] = p3[i] flag[4*i + 3] = p4[i] return bytes(flag) def main(): # Texte chiffré (hexadécimal) tel que donné dans l'énoncé cipher_hex = ( "1E63276060380C232663" # part1 (10 octets chiffrés) "4B6868687C3C3A7B6C77" # part2 (10 octets chiffrés) "0A1F040542081F410550" # part3 (10 octets chiffrés) "356122390E220E35602C" # part4 (10 octets chiffrés) ) # 1) On découpe en 4 blocs de 20 hex-chars (=> 10 octets chacun) blocs = [cipher_hex[i:i+20] for i in range(0, len(cipher_hex), 20)] c1 = hex_to_bytes(blocs[0]) # Partie 1 (10 octets) chiffrés c2 = hex_to_bytes(blocs[1]) # Partie 2 (10 octets) chiffrés c3 = hex_to_bytes(blocs[2]) # Partie 3 (10 octets) chiffrés c4 = hex_to_bytes(blocs[3]) # Partie 4 (10 octets) chiffrés # 2) On applique l’inverse des clés découvertes par rétro-ingénierie: xorK = 0x53 # clé unique utilisée pour la partie 1 (assembleur fixe à 83d) polyK = 9 # clé additif utilisée pour la partie 2 (9d) optK = 113 # clé XOR utilisée pour la partie 3 (113d) genK = 0x51 # clé XOR utilisée pour la partie 4 (81d) p1 = decrypt_part1(c1, xorK) p2 = decrypt_part2(c2, polyK) p3 = decrypt_part3(c3, optK) p4 = decrypt_part4(c4, genK) # Affichage intermédiaire (pour vérification) : print("Partie 1 (déchiffrée) :", p1.decode('ascii', errors='ignore')) print("Partie 2 (déchiffrée) :", p2.decode('ascii', errors='ignore')) print("Partie 3 (déchiffrée) :", p3.decode('ascii', errors='ignore')) print("Partie 4 (déchiffrée) :", p4.decode('ascii', errors='ignore')) print() # 3) On réassemble en intercalant pour obtenir le flag complet flag = interleave_parts(p1, p2, p3, p4) print("Flag reconstitué :", flag.decode('ascii')) if __name__ == "__main__": main()