Reversconstrictor
Catégorie: Rétro-Ingénierie - Difficulté: Facile
Description:

Solution:
Nous avons à disposition un binaire dont nous ne connaissons pas le contenu. En regardant le titre du challenge, nous avons une petite idée de ce qu'il peut cacher mais nous allons faire du repérage :

Bon cela nous apporte quelques infos, mais nous allons surtout nous focus sur la partie "python" qui est l'objectif pour lequel le titre référence (boa constrictor / python...).
Pour aller plus loin, nous allons utiliser un outil nommé pyinstxtractor afin de récupérer les sources contenues dans le binaire :

Et maintenant nous avons de nouveaux fichiers, dont un .pyc qui nous intéresse beaucoup :

À partir de maintenant, il s'agit d'un simple reverse de .pyc, comme dans chaque CTF. Nous allons donc procéder étape par étape en commençant par décompiler le .pyc :

Nous obtenons ce code python en clair, c'est merveilleux :
import tkinter as tk
import importlib.util as importlib
import os
import sys
def import_module():
module_path = os.path.join(sys._MEIPASS, 'modules/encrypt_key.cpython-39.pyc')
spec = importlib.util.spec_from_file_location('nom_module', module_path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
module = import_module()
def xor(a, b):
return bytes((lambda .0: for x, y in .0:
x ^ y)(zip(a, b)))
def validate_password(password):
if xor(module.encode_password(password.encode('ascii')), module.encrypt_key(0x6D39D56F8A40A6BBE43A82A53B2C762EA780C21A32C6B3EF765D3A54F3432432F3E6D39D56F8A40A6BBE43A82A53B2C762EA780C21A32C6B3EF765D3A54F3432432F3E)) == b'\xe9J\x1aB\xe2\xc5\xf3S\'\xd6>\n$\x94\x1a\x07\'F\xc6\xa1\x07\xb7\xcc\xec\xe1\x84\xec\xac\xe4\xd64\x8f\xc3\x12\x04\x16$n\x15\xec\xe1\xaee5\xc7\xecOX"\x98EO\x1f2\xb4\x15\xc4\xed\xf4\xcd$\xd3\xd3u\xc2\xf8\xc6\xae\x06\x08\xcd\xff\xe0(\xe9\xb0\xe7\xde6\x90\xcc\xfd\x02}%\x1a\x1a\xc9#\x10\xc2\x86\x06\x08\xcd\xfe&\xb8K\x0f)\x9a\xb6\xb9\x02\x17\xa0\xd8\xe4]\x98\xf5*\x154<\x06\x875\xbd\x05@\xe6\x88\xe3&6%\xcc\x18\x06\\%\xa4\x1a7!\xfe\xc3\xae\x06\x08\xcd\xff\xe2\x18\xe2x\xe0\x927x\r\xfa\xa6\xbd\xe67\x97\xf7\xe5)f\x94\xc8\xbdv\r\xef\x12\x1bZ\xe8e\xf3S\'\xd6>\n"8\x1be\x9c\xdf\xe8\x9b\x06\xb7\x0b3V\x1f\xedN\x87\xbbI!C>8z%\xc0\xeaM\xb5\xd1p\xd1\x0f|A\xd7B\x03\xc54\xd5T\xb9\xfd\x88;\xbf\x10\x81L\x90L\x0b\xff\xed\xe1\xe5dQ\xc4\x17\xd5\xafUl\xec':
label.config('Mot de passe correct !', **('text',))
else:
label.config('Mot de passe incorrect !', **('text',))
root = tk.Tk()
root.title('Rêve en Python')
root.geometry('300x200')
label = tk.Label(root, 'Entrez le mot de passe :', **('text',))
label.pack(10, **('pady',))
entry_password = tk.Entry(root, 20, **('width',))
entry_password.pack(5, **('pady',))
validate_button = tk.Button(root, 'Valider', (lambda : validate_password(entry_password.get())), **('text', 'command'))
validate_button.pack(5, **('pady',))
root.mainloop()
À ce que nous pouvons voir à la ligne 7, il fait appel à un autre fichier : modules/encrypt_key.cpython-39.pyc
Nous allons donc procéder de la même manière pour récupérer son code :

Alors ici ça n'a pas fonctionné comme précédemment, et c'est assez étrange... Mais pour réparer ça, j'ai utilisé un service en ligne qui l'a fait sans souci.
Voici le code complet :
def encrypt_key(key):
for _ in range(100):
key <<= 1
key ^= 28822426245224264980979321341345830828356797782055329828562090481380116178932605047916536097759912367507574811007140506795012665313071649493970094581868771201400995139374360577815608383329090103210656811239441222567030843263175788045939510447941542030750571932256144094323129497438168350642218592361235189388565721332503735143045072480576796605508643464856724395849208355789353984327745285879034330837484467961665782705864897713128496005106869640456653659559683849265346017315593794571762231394086134713064707220190583043994656101899455622598958597972721263137718293457676368185324168958757297967087684
key >>= 1
key &= 109051226663159329753852712655361641732299866997884252194334358336942491585415479875295813282055589726130083576518139892456534344478282750316593419244506314188673419372046405908627553201165095804542555714314672295089748281211620003277814236498206175511101163526217782040005646393337007890354251307329101818031816904956050245603083420859836404091717206011901780489732016582515541865280619889684277395118340072030441380240101696073919110415984233261149866579378832210321108153068228794726382510988474475602148132965289631295957683363624794221017463029077341994983080187012597951461555452400067297865364263
key -= 30192244264443276570339417266820821425000308120852003855173657187210512774975257998930431381307567916542287355804911140923844613245045835307895499468937998079600051550088167870781968330783540846793847843565906160661919016260958589152016311435717470936180510788226717267994601294202007594965057260235235670775632982187962998589201876115127904069810375748935472032501147620317590198256980636488859805187975959355302150868894406858968080766062052395340075809777559133433991365763567236747759028423915038949095093046477455353960143902941004723715753803085977215629377804159634990715729987578541999707000127
key ^= 28822426245224264980979321341345830828356797782055329828562090481380116178932605047916536097759912367507574811007140506795012665313071649493970094581868771201400995139374360577815608383329090103210656811239441222567030843263175788045939510447941542030750571932256144094323129497438168350642218592361235189388565721332503735143045072480576796605508643464856724395849208355789353984327745285879034330837484467961665782705864897713128496005106869640456653659559683849265346017315593794571762231394086134713064707220190583043994656101899455622598958597972721263137718293457676368185324168958757297967087684
key <<= 1
key += 4324354
key >>= 1
key = abs(key)
encrypted_bytes = key.to_bytes((key.bit_length() + 7) // 8, byteorder='big')
return encrypted_bytes
def encode_password(password):
a = b''
x_list = [110, -34, -230]
for i in range(len(password)):
b = password[i] // 11 + 11
c = password[i] % 11
d = b + c
e = b * c
r = []
for i in range(3):
x = x_list[i]
y = x ** 2 - d * x + e
assert y > 0 and y < 65536 and (y not in r)
r.append(y)
else:
for i in range(3):
a += bytes.fromhex(f'{r[i] // 256:02x}')
a += bytes.fromhex(f'{r[i] % 256:02x}')
else:
return a
Nous avons donc toutes les pièces en main pour déchiffrer tout cela. Pour faire ça automatiquement, nous allons créer un script Python (pour rester dans l'ambiance) :
# ---------------------------------------------------------------------------
# 1) Constantes du challenge
# ---------------------------------------------------------------------------
TARGET = bytes.fromhex(
"e94a1a42e2c5f35327d63e0a24941a072746c6a107b7ccece184ecace4d6348f"
"c3120416246e15ece1ae6535c7ec4f582298454f1f32b415c4edf4cd24d3d375"
"c2f8c6ae0608cdffe028e9b0e7de3690ccfd027d251a1ac92310c2860608cdfe"
"26b84b0f299ab6b90217a0d8e45d98f52a15343c068735bd0540e688e3263625"
"cc18065c25a41a3721fec3ae0608cdffe218e278e09237780dfaa6bde63797f7"
"e5296694c8bd760def121b5ae865f35327d63e0a22381b659cdfe89b06b70b33"
"561fed4e87bb4921433e387a25c0ea4db5d170d10f7c41d74203c534d554b9fd"
"883bbf10814c904c0bffede1e56451c417d5af556cec"
)
CST = int(
"6D39D56F8A40A6BBE43A82A53B2C762EA780C21A32C6B3EF765D3A54F3432432F3E"
"6D39D56F8A40A6BBE43A82A53B2C762EA780C21A32C6B3EF765D3A54F3432432F3E",
16
)
# ---------------------------------------------------------------------------
# 2) encrypt_key ― repris mot-pour-mot du .pyc
# ---------------------------------------------------------------------------
def encrypt_key(key: int) -> bytes:
for _ in range(100):
key <<= 1
key ^= 0x40440440440440444044044044044044404404404404404440440440440440444044044044044044404404404404404440440440440440444044044044044044404404404404404440440440440440444044044044044044404404404404404440440440440440444044044044044044404404404404404440440440440440444044044044044044404404404404404440440440440440444044044044044044404404404404404440440440440440444044044044044044404404404404404440440440440440444044044044044044404404404404404440440440440440444044440440444044404440440440440440444044440440444044
key >>= 1
key &= 0xF3271ADF3271ADF3F3271ADF3271ADF3F3271ADF3271ADF3F3271ADF3271ADF3F3271ADF3271ADF33271ADF3F3271ADF3271ADF3F3271ADFF3271ADF3271ADF3F3271ADF3271ADF3F3271ADF3271ADF3F3271ADF3271ADF3F3271ADF3271ADF33271ADF3F3271ADF3271ADF3F3271ADFF3271ADF3271ADF3F3271ADF3271ADF3F3271ADF3271ADF3F3271ADF3271ADF3F3271ADF3271ADF33271ADF3F3271ADF3271ADF3F3271ADFF3271ADF3271ADF3F3271ADF3271ADF3F3271ADF3271ADF3F3271ADF3271ADF3F3271ADF3271ADF33271ADF3F3271ADF3271ADF3F3271ADF1ADFF3271ADF1ADFADF3F3271ADF1ADFF3271ADF1ADFADF3F327
key -= 0x4351EAC5DB5A0D3F31513511EAC5DB5A0D3F3521EAC5DB5A0D3F3151EAC5DB5A0D3F2143EAC5DB5AEAC5DB5A0D3F3151EAC5DB5A0D3F31514351EAC5DB5A0D3F31513511EAC5DB5A0D3F3521EAC5DB5A0D3F3151EAC5DB5A0D3F2143EAC5DB5AEAC5DB5A0D3F3151EAC5DB5A0D3F31514351EAC5DB5A0D3F31513511EAC5DB5A0D3F3521EAC5DB5A0D3F3151EAC5DB5A0D3F2143EAC5DB5AEAC5DB5A0D3F3151EAC5DB5A0D3F31514351EAC5DB5A0D3F31513511EAC5DB5A0D3F3521EAC5DB5A0D3F3151EAC5DB5A0D3F2143EAC5DB5AEAC5DB5A0D3F3151EAC5DB5A0D3F315131510D3F31513151DB5A0D3F315131510D3F31513151DB5A0D3F
key ^= 0x40440440440440444044044044044044404404404404404440440440440440444044044044044044404404404404404440440440440440444044044044044044404404404404404440440440440440444044044044044044404404404404404440440440440440444044044044044044404404404404404440440440440440444044044044044044404404404404404440440440440440444044044044044044404404404404404440440440440440444044044044044044404404404404404440440440440440444044044044044044404404404404404440440440440440444044440440444044404440440440440440444044440440444044
key <<= 1
key += 4324354
key >>= 1
key = abs(key)
return key.to_bytes((key.bit_length() + 7) // 8, "big")
# ---------------------------------------------------------------------------
# 3) encode_byte (une lettre -> 6 octets) et table inverse
# ---------------------------------------------------------------------------
X = [110, -34, -230]
def encode_byte(p: int) -> bytes:
b, c = p // 11 + 11, p % 11
d, e = b + c, b * c
r = [x * x - d * x + e for x in X] # trois entiers 16 bits
out = bytearray()
for y in r:
out += y.to_bytes(2, "big")
return bytes(out)
# table “6 octets → caractère”
DEC = {encode_byte(p): p for p in range(256)}
# ---------------------------------------------------------------------------
# 4) Décodage : TARGET ⊕ encrypt_key(CST) → mot de passe
# ---------------------------------------------------------------------------
enc_key = encrypt_key(CST)
encoded = bytes(t ^ k for t, k in zip(TARGET, enc_key))
password = "".join(
chr(DEC[encoded[i : i + 6]]) # 6 octets = 1 caractère
for i in range(0, len(encoded), 6)
)
print(password)

Last updated
Was this helpful?