المجتمع الأكبر للعرب في مجال التك

تحميل من Google Playتحميل من App Store

CAT Reloaded CTF 2025 - Pickle Challenge walkthrough

Amer Ahmed منذ 5 أشهر

دا حلي ل Pickle CTF يوم ما اشتغلنا انا والتيم


بعد شوية معاناة في تسطيب python 3.9 , فكيت الrar file وكان فيه تلت فايلات

run.py

rehyd.py

chall.pkl

خلينا نشوف run.py الاول


هنا هو بيعمل import لrehyd و بيفتح chall.pkl فالread byte mode 

لما تشوف محتويات chall.pkl هتلاقيها مشوهة او obfuscated 


طيب خلينا نشوف rehyd.py


هنا بيفك ضغط كود مموه وبيعمل execution طب خلينا نجرب نشغل run.py ونشوف هيحصل ايه

يدوبك بس بيشوف جبت الحل ولا لا

طبعا دا الى حد ما اسهل من تحليل exe او اي compiled code

نرجع لrehyd.py انا عايز دلوقت اشوف الكود بعد ما بيتفك الضغط و الdeobfuscation بتاع ايا كان اللي بيعمله.

اسهل حاجة اني اعمله dump بالfile operations بتاعت python و اشوف الكود بس هما عقدوها حنة زيادة.

مرحلة فك الضغط و نقلها للمتغير code بيخلي نوع الobject دا من class code

من خصائص الcode object/class في python انه بيحتوي عالcompiled bytecode و ايوه في حاجة اسمها compiled python وهي عملية تحويل الpython source code لbytecode بتبقى عبارة عن instructions بتنفذها الPVM (python vm) والextenstion بتاعها .pyc

بعدها اتخضيت عشان اول مرة اسمع عن debugging python bytecode بس حلها كان سهل عشان اول ما يبقى عندي الbytecode نفسه اقدر في ثانية احوله لpython source code عادي مش زي صعوبة C/C++

قولت خلاص اشوف حل اعرف منه ا dump الdisassembled bytecode لفايل لوحده عشان اكمل تحاليل عليه.

فا استعنت بالAI انه يتولى موضوع حساب الmagic bytes والheaders بتاعت الpyc ويعملها dump فالاخر


كدا ممكن اشغل run.py تاني



عملت print لللdisassembled bytecode و سيفت الbytecode في فايل. خلينا نشوف محتوى الفايل دا ايه

طبعا عبارة عن unreadable data بس بقينا اقرب للحل, دلوقت عايزين نحوله لreadable source code

هخش ادور على اي python bytecode decompiler اونلاين وادخل الفايل وانزل الsource code

لما فتحت الdecompiled file اللي هي المفروض اخر مرحلة خلاص اتفاجأت بالمنظر دا

حوالي 3500 سطر من الfunctions والcalculations ساعتها بقى انا فصلت بتاع خمس ساعات ورجعتلها تاني
الفايل عبارة عن functions بس و كل واحدة فيهم بتعمل الtests بتاعتها فا اكيد انا مش هشتغل bruteforce واحسب كل function لوحدها
فكرت هو فين اصلا الentry point بتاعت الفايل يعني انهي function بيتعملها call

روحت رجعت على rehyd.py وهنا لقيت انها بترجع حاجة اسمها ns[entry]


قولت بس خلاص اعملها print كمان و اشغل run.py اشوف ايه دي



بس خلاص كدا عرفت اني Chimpanzini_Bananini هي الحل للموضوع كله 

كدا نبتدي نحلل الfunction دي على رواقة ونطلع الflag واحدة واحدة


دي الfunction snippet 


def Chimpanzini_Bananini():
    password = input('Enter the Flag: ')
    part1 = xor(password[0:5].encode(), password[5:10].encode())
    if part1.hex() != '302e0b1933':
        print('wrong')
        return
    part2 = zlib.crc32(password[10:14].encode())
    if part2 != 3979310991:
        print('wrong')
        return
    part3 = zlib.crc32(password[14:18].encode())
    if part3 != 448183154:
        print('wrong')
        return
    part4 = xor(password[0:18].encode(), password[18:36].encode())
    if part4.hex() != '70373134241b5c6b2d2c6b42076f2c442a2b':
        print('wrong')
        return
    part5 = hashlib.md5(password[36:38].encode()).hexdigest()
    if part5 != '346b81a32e7007eccadf60252bb599f0':
        print('wrong')
        return
    part6 = hashlib.md5(password[38:40].encode()).hexdigest()
    if part6 != '2c3ba657da75eab82c88c429fbbf2207':
        print('wrong')
        return
    part7 = tralalero_tralala('flag{real_is_rare__fake_is_everywhere}', password[40:58])
    if part7 != '3856abb119718a174973a5fbbf46727f419c':
        print('wrong')
        return
    print('Flag is correct!')


نشوف part1:

واضح انه بيعمل xor لجزء من الinput بتاعك مع جزء تاني من الinput بتاعك و يقارن النتيجة بالhex value دي 302e0b1933

هنا مش هقدر اعمل اي reverse engineering عشان مش عارف ولا جزء من المعادلة غير النتيجة وانا محتاج عالاقل جزئين من المعادلة.


عملت skip ودخلت على part2:

هنا اسهل شوية, بياخد جزء من الinput وبيعمله crc32 checksum وبيقارن النتيجة لhardcoded value

بس خلاص كدا هنكتب سكريبت يعكس الcrc32 checksum وياخد الhardcoded value ويرجعلنا جزء من الflag

هنا استخدمت الAI في انه يكتبلي سكريبت يعكس الalgorithm ودي كانت النتيجة بعد ما عدلت:

import zlib
import itertools
import string

target_crc = 3979310991

# You can customize charset here, e.g.:
charset = string.ascii_letters + string.digits + string.punctuation + ' '

def brute_force_crc32(target_crc, length=4):
    print(f"Starting brute force for CRC32={target_crc} over {length} bytes...")
    count = 0
    for candidate_tuple in itertools.product(charset, repeat=length):
        candidate = ''.join(candidate_tuple)
        crc = zlib.crc32(candidate.encode())
        count += 1
        if crc == target_crc:
            print(f"Found match: '{candidate}'")
            # Return here if you want only first result, or continue to find all
            return candidate
        if count % 1000000 == 0:
            print(f"Checked {count} candidates so far...")
    print("No matches found.")
    return None

result = brute_force_crc32(target_crc)
print("Result:", result)


كدا طلعنا password[10:14] و هتساوي _4ve


نخش على part3 :

نفس الكلام بس value مختلفة فا طبقت نفس السكريبت بس غيرت الtarget_crc ل 448183154

كدا طلعنا password[14:18] و هيساوي _Y0u


نخش على part4: 

هنعمله skip دلوقت عشان نفس فكرة part1


نخش على part5: 

هنا بيعمل hash لجزء من الpassword بالmd5 وبيحوله لhex ويقارنه بhardcoded hex value والmd5 طبعا ضعيف جدا

استخدمت الAI عشان يcrack الmd5 hash و طلعت بالسكريبت دا بعد التعديل:

import hashlib
import string
import itertools

target_hash = '346b81a32e7007eccadf60252bb599f0'
charset = string.printable.strip()  # printable ASCII, without whitespace

def brute_force_md5(target_hash):
    for c1, c2 in itertools.product(charset, repeat=2):
        candidate = c1 + c2
        if hashlib.md5(candidate.encode()).hexdigest() == target_hash:
            print(f"Found match: '{candidate}'")
            return candidate
    print("No match found.")
    return None

result = brute_force_md5(target_hash)
print("Result:", result)


كدا طلعنا password[36:38] و هيساوي h1


نخش على part6:

نفس فكرة الmd5 hashing فا استخدمت نفس السكريبت بس غيرت الtarget hash

كدا طلعنا password[38:40] وهيساوي _s


نخش على part7:

هنا اختلفت شوية, هو بcall function اسمها tralalero_tralala باتنين args واحد فيهم hardcoded والتاني جزء من الinput وبيقارن الreturn value بvalue تانية

هنا دخلت اشوف الfunction دي بتعمل ايه

def tralalero_tralala(key, plain):
    S = list(range(256))
    j = 0
    key_bytes = key.encode()
    for i in range(256):
        j = (j + S[i] + key_bytes[i % len(key_bytes)]) % 256
        S[i], S[j] = (S[j], S[i])
    i = j = 0
    result = []
    for char in plain.encode():
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = (S[j], S[i])
        K = S[(S[i] + S[j]) % 256]
        result.append(char ^ K)
    return ''.join((f'{b:02x}' for b in result))

دي encryption algorithm بسيطة جدا واستخدمت الAI في انه اعرف ايه هي و طلعت RC4 و RC4 دي symmetric encryption algorithm فا مفتاح التشفير هو هو مفتاح فك التفشير فا عشان تجيب الplaintext هتروح لأي online rc4 decryption tool input و هتحط الencyrpted value  دي 3856abb119718a174973a5fbbf46727f419c و تحط custom decryption key اللي هي دي flag{real_is_rare__fake_is_everywhere}



دلوقت احنا عندنا الاجزاء دي من الflag

password[0:5] = CATF{                                     default format

password[5:10] = UNKNOWN

password[10:14] = 4ve_

password[14:18] = Y0u_

password[18:36] = UNKNOWN

password[36:38] = h1

password[38:40] = s_

password[40:58] = cucumb3red_th1ng?}


كدا فاضل جزئين ناقصين فالflag فا نرجع على part 1:

كدا احنا عندنا جزئين من معادلة الXOR اللي هما النتيجة و password[0:5] 

استعنت بالAI وكتبت سكريبت عشان يطلع الvalue الناقصة ودا السكريبت:


import string

def xor_bytes(b1, b2):
    return bytes([x ^ y for x, y in zip(b1, b2)])

# Fixed input A
A = b'CATF{'

# XOR target
target_xor = bytes.fromhex('302e0b1933')

# Compute B directly: B = A XOR target
B = xor_bytes(A, target_xor)
print(f"len(A) => {len(A)}")
print(f"len(B) => {len(B)}")
# Check if B is printable
if all(32 <= b < 127 for b in B):
    print(f"A = {A.decode()}")
    print(f"B = {B.decode()}")
    print(f"A XOR B = {target_xor.hex()}")
else:
    print(f"B contains non-printable characters: {B.decode(errors='replace')}")




كدا طلعنا password[5:10] و بيساوي so__H


هنعمل نفس الكلام في part4:

بس هنغير المتغير A ل CATF{so__H4ve_Y0u_

وهنغير الtarget xor للhex اللي هيتقارن بيه part4


و هيطلع باقي الflag اللي هو password[18:36] هيساوي 

3ver_h34rd_4bout_t


فالflag على بعض هيبقى

CATF{so__H4ve_Y0u_3ver_h34rd_4bout_th1s_cucumb3red_th1ng?}



0 ردود