Skip to main content

(Facile) Weak Signature

Description

Le but est de trouver une faille dans le serveur afin de pouvoir lire le fichier flag.txt

En analysant le script du serveur que l'on nous envoie ainsi que le Readme, on découvre que le serveur permet de signer / lire une archive spécifique avec une signature spécifique. On nous transmet d'ailleur une archive d'exemple qui print un message lors de l'exécution : Hello, CTF Player!

Analyse

Pour bypasse le serveur il nous faut pouvoir lui envoyer une archive signée corectement qui permmettent de lire le fichier flag.txt

Pour cela on va commencer par analyser l'archive que l'on a reçu afin de comprendre comment elle fonctionne :

image-1654952430353.png

On observe donc bien la structuration donné par le Readme : 

  • Magic number (5 bytes) : 01 5A 53 69 67
  • \x02 : 02
  • Signature of the data (300 bytes, 0-padded, big endian)
  • \x03 : 03
  • Size of data section (4 bytes, 0-padded, big endian)
  • \x04 : 04

Et notre data section pour terminer. Cette dernière débute au caractère 312 (5 + 1 + 300 + 1 + 4 +1)

image-1654952549729.png

Pour que cette archive soit reconnue par le serveur, il faut qu'elle soit signée. Hors nous ne possédons pas la clé privée pour pouvoir signer l'archive avec le programme.

En analysant le code de la fonction de signature, on remarque cela :

def checksum(data: bytes) -> int:
    # Sum the integer value of each byte and multiply the result by the length
    chksum = sum(data) * len(data)

    return chksum


def compute_signature(data: bytes, private_key: int, mod: int) -> int:
    # Compute the checksum
    chksum = checksum(data)
    # Sign it
    signature = pow(chksum, private_key, mod)

    return signature

La signature ne se fait que sur le checksum et ce dernier est égal à la somme des valeurs de notre payload * la taille de ce dernier.

Ainsi, pour obtenir une archive signée, il suffit de créer un payload dont le checksum est le même que celui de l'Archive que nous avons en notre possession.

Solution

Notre but est d'obtenir le contenu du fichier flag.txt. Notre charge utile de notre payload sera donc :

print((open("flag.txt", "r").read()))

Le checksum que nous devons obtenir est de 1235738

Il existe de nombreuses manière d'arriver à ce checksum, pour ma part j'ai choisi celle qui me sembait être la plus simple j'ai commencé par créer un payload de la même taille que le code de l'archive initiale. Ainsi il suffit que ma "somme de valeurs" soit la même pour que le checksum complet soit le même.

Pour ce faire il me faut donc un payload de 122 caractères:

#AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
print((open("flag.txt", "r").read()))

La lettre A (en majuscule) est la plus petite valeur en ASCII pour une lettre ça nous permet donc d'avoir le score minimum si nous avons que des lettres. On calcul alors les checksums :

image-1655539993288.png

Le but à présent est de modifier le payload sans changer sa taille afin d'obtenir le même checksum. Pour cela on va se rapprocher du checksum de l'archive en modifiant des A par des a ce qui va augmenter notre checksum :

#AAAAAAAAAAAAAAAAAAAAAAAAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
print((open("flag.txt", "r").read()))

image-1655540616151.png

Notre taille est de 122 caractère 1220 = 122 * 10 il suffit alors de transformer un A en un K 11ème lettre de l'alphabet soit 10* plus de point que notre A :

#AAAAAAAAAAAAAAAAAAAAAAAAKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
print((open("flag.txt", "r").read()))

image-1655540739924.png

Ne reste plus qu'à remplacer la fin de l'archive par ce payload :

file = open("script.py.zsig", "rb").read()
data = b'#AAAAAAAAAAAAAAAAAAAAAAAAKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\nprint((open("flag.txt", "r").read()))'
f = open("new.py.zsig", "wb")
f.write(file[:312] + data)

Et de l'envoyer au serveur encodé en base64 :

import socket
import base64

client = socket.socket()
client.connect(('challenge.404ctf.fr',32441))
init = client.recv(4096).split(b'\n')

data = open("new.py.zsig", "rb").read()
encoded = base64.b64encode(data)
client.send(encoded + b"\n")

print(client.recv(4096).decode())

On obtient notre Flag en retour : 404CTF{Th1s_Ch3cksum_W4s_Tr4sh}