Zeecka zeecka

Python Reversing

TJCTF 2018 - RE (40 pts)

TJCTF 2018: Python Reversing

Détails du challenge

EventChallengeCategoryPointsSolves
TJCTF 2018Python ReversingReverse Engineering40221 solves

Téléchargement : source.py - md5: 544000ae1981ca703e22201fdbfedf14

Description

J’ai trouvé ce fichier de vérification de flag et il est plutôt vulnérable

TL;DR

Le script n’était pas vraiment obfusqué. Nous avions l’output du flag hashé et devions retrouver l’input.
Le texte en clair était d’abord randomisé avec l’équivalent du code suivant :

flag = [random.randint(1,5) * ord(x) for x in flag]

Ensuite un xor était appliqué avec la clé suivante : ligma_sugma_sugondese_
Enfin le résultat était affiché en binaire, chaque lettre pouvait faire 8 à 10 chiffres et était concaténée.

Un bruteforce / une cryptanalyse efficace nous a donné le flag.

Méthodologie

Analyse des fichiers

La première chose que nous avons faite a été d’ouvrir le code source source.py - md5: 544000ae1981ca703e22201fdbfedf14.

import numpy as np

flag = 'redacted'

np.random.seed(12345)
arr = np.array([ord(c) for c in flag])
other = np.random.randint(1,5,(len(flag)))
arr = np.multiply(arr,other)

b = [x for x in arr]
lmao = [ord(x) for x in ''.join(['ligma_sugma_sugondese_'*5])]
c = [b[i]^lmao[i] for i,j in enumerate(b)]
print(''.join(bin(x)[2:].zfill(8) for x in c))

# original_output was 1001100001011110110100001100001010000011110101001100100011101111110100011111010101010000000110000011101101110000101111101010111011100101000011011010110010100001100010001010101001100001110110100110011101

En regardant le code, on peut voir un peu d’aléatoire, un xor et une conversion en binaire. Cependant, en regardant de plus près, la taille de original_output n’est pas un multiple de 8, ce qui signifie qu’il y aura plus de bruteforce et de cryptanalyse que prévu.

Reversing

Un code vaut mieux qu’un texte, voici donc le code reversé commenté

import numpy as np # Import numpy array library

flag = 'redacted' # Injection point
np.random.seed(12345) # Init random seed, no need to touch it
# String to list of ascii code: "abc" => [97,98,99]
arr = np.array([ord(c) for c in flag])
# Mask with integer in [1,5[ with the same size as flag
other = np.random.randint(1,5,(len(flag)))
# Array where ascii code is multiplied with the corresponding ranom int
arr = np.multiply(arr,other)


"""
Example here:
flag = redacted
arr = [114, 101, 100, 97, 99, 116, 101, 100]
other = [1,   1,   4,  1,  1,   1,   1,   2]  # Could be anything else
# After multiply:
arr = [114, 101, 400,  97,  99, 116, 101, 200]
"""

# At this point we got our input with random multiplication up to 5 * the original value
b = [x for x in arr] # Convert from numpy array to python array
# String to list of ascii code: convert ligma... key to list of integer for the xor
lmao = [ord(x) for x in ''.join(['ligma_sugma_sugondese_'*5])]
# c = XOR(arr,lmao)
c = [b[i]^lmao[i] for i,j in enumerate(b)]

# Print output
# /!\ While testing, bin(x)[2:].zfill(8) can be up to 10 digits !
print(''.join(bin(x)[2:].zfill(8) for x in c))

# original_output was 1001100001011110110100001100001010000011110101001100100011101111110100011111010101010000000110000011101101110000101111101010111011100101000011011010110010100001100010001010101001100001110110100110011101

Bruteforcing

À cause de la multiplication aléatoire, notre algorithme n’est pas déterministe, nous devons choisir entre différentes lettres lorsqu’il y a plusieurs possibilités. J’ai choisi de bruteforcer caractère par caractère, en partant d’un flag vide et de l’output complet, et en retirant progressivement l’output attendu :

Étape 1

flag = ""
original_output = "1001100001011110110100001100001010000011110101001100100011101111110100011111010101010000000110000011101101110000101111101010111011100101000011011010110010100001100010001010101001100001110110100110011101"

import string # import printable char
for l in string.printable: # for each printable char we test
    for k in range(1,5): # Testing the differents multiplication from 1x to 4x
        letter = (bin((ord(l)*k)^lmao[len(a)])[2:].zfill(8)) # xor and zfill
        if outp.startswith(letter):
            print(l+"  =>  "+letter)
t  =>  100110000
z  =>  10011000
W  =>  100110000
=  =>  10011000

J’ai choisi t pour tjctf. On retire le binaire correspondant du début de l’output, et on ajoute la lettre au flag

Étape 2

flag = "t"
original_output = "1011110110100001100001010000011110101001100100011101111110100011111010101010000000110000011101101110000101111101010111011100101000011011010110010100001100010001010101001100001110110100110011101"

import string # import printable char
for l in string.printable: # for each printable char we test
    for k in range(1,5): # Testing the differents multiplication from 1x to 4x
        letter = (bin((ord(l)*k)^lmao[len(a)])[2:].zfill(8)) # xor and zfill
        if outp.startswith(letter):
            print(l+"  =>  "+letter)
5  =>  10111101
j  =>  10111101

J’ai choisi j pour tjctf.

Étape 6

flag = "tjctf"
original_output = "10101001100100011101111110100011111010101010000000110000011101101110000101111101010111011100101000011011010110010100001100010001010101001100001110110100110011101"

import string # import printable char
for l in string.printable: # for each printable char we test
    for k in range(1,5): # Testing the differents multiplication from 1x to 4x
        letter = (bin((ord(l)*k)^lmao[len(a)])[2:].zfill(8)) # xor and zfill
        if outp.startswith(letter):
            print(l+"  =>  "+letter)
C  =>  101010011
R  =>  10101001
{  =>  10101001

J’ai choisi { pour tjctf{}.

Choisir entre les lettres n’était pas trop difficile car on s’attendait à des mots comme « python ».

Dernière étape

flag = "tjctf{pYth0n_1s_tr1v14l"
original_output = "110011101"

import string # import printable char
for l in string.printable: # for each printable char we test
    for k in range(1,5): # Testing the differents multiplication from 1x to 4x
        letter = (bin((ord(l)*k)^lmao[len(a)])[2:].zfill(8)) # xor and zfill
        if outp.startswith(letter):
            print(l+"  =>  "+letter)
# }  =>  110011101

Flag !

Flag

tjctf{pYth0n_1s_tr1v14l}

Zeecka