← Back to blog

SpookyCheck

Challenge

  • CTF: Hack The Boo 2023
  • Name: SpookyCheck
  • Category: Reversing
  • Difficulty: Medium
  • Points: 350
  • Description: My new tool will check if your password is spooky enough for use during Halloween - but watch out for snakes…
  • Objective: Python .pyc Reversing

Files

Download: rev_spookycheck.zip

Writeup

The challenge file of check.pyc was identified as a byte-compiled Python file after analyzing it using the file utility.

file check.pyc
check.pyc: Byte-compiled Python module for CPython 3.11, timestamp-based, .py timestamp: Mon Sep  4 15:32:51 2023 UTC, .py size: 656 bytes

Analyzing the binary using strings, we can identify a SUP3RS3CR3TK3Y:

strings check.pyc
SUP3RS3CR3TK3Ys
len)
check.py
<listcomp>z
transform.<locals>.<listcomp>
    enumerate
flags
    transformr
CHECKr
checkr
__main__u
 Welcome to SpookyCheck
 Enter your password for spooky evaluation
 Well done, you're spookier than most!
 Not spooky enough, please try again later
    bytearrayr
__name__
print
input
encode
<module>r

We can do dynamic analysis by importing the library and seeing what type of functions/variables are available in check.pyc.

python3
>>> import check
>>> print(check.CHECK)
bytearray(b'\xe9\xef\xc0V\x8d\x8a\x05\xbe\x8ek\xd9yX\x8b\x89\xd3\x8c\xfa\xdexu\xbe\xdf1\xde\xb6\\')
>>> print(check.KEY)
b'SUP3RS3CR3TK3Y'

This file can be attempted to be decompiled to source-code using pycdc of Python Decompyle++. Unfortunately, it isn’t fully supported for Python 3.11.

pycdc check.pyc
# Source Generated with Decompyle++
# File: check.pyc (Python 3.11)

KEY = b'SUP3RS3CR3TK3Y'
CHECK = bytearray(b'\xe9\xef\xc0V\x8d\x8a\x05\xbe\x8ek\xd9yX\x8b\x89\xd3\x8c\xfa\xdexu\xbe\xdf1\xde\xb6\\')

def transform(flag):
    return enumerate(flag)()


def check(flag):
Error decompyling check.pyc: vector::_M_range_check: __n (which is 2) >= this->size() (which is 2)

This file can be attempted to be disassembled to byte-code using pycdas of Python Decompyle++. This was successful.

pycdas check.pyc
check.pyc (Python 3.11)
[Code]
    File Name: check.py
    Object Name: <module>
    Qualified Name: <module>
    Arg Count: 0
    Pos Only Arg Count: 0
    KW Only Arg Count: 0
    Stack Size: 4
    Flags: 0x00000000
    [Names]
        'KEY'
        'bytearray'
        'CHECK'
        'transform'
        'check'
        '__name__'
        'print'
        'input'
        'inp'
        'encode'
    [Locals+Names]
    [Constants]
        b'SUP3RS3CR3TK3Y'
        b'\xe9\xef\xc0V\x8d\x8a\x05\xbe\x8ek\xd9yX\x8b\x89\xd3\x8c\xfa\xdexu\xbe\xdf1\xde\xb6\\'
        [Code]
            File Name: check.py
            Object Name: transform
            Qualified Name: transform
            Arg Count: 1
            Pos Only Arg Count: 0
            KW Only Arg Count: 0
            Stack Size: 4
            Flags: 0x00000003 (CO_OPTIMIZED | CO_NEWLOCALS)
            [Names]
                'enumerate'
            [Locals+Names]
                'flag'
            [Constants]
                None
                [Code]
                    File Name: check.py
                    Object Name: <listcomp>
                    Qualified Name: transform.<locals>.<listcomp>
                    Arg Count: 1
                    Pos Only Arg Count: 0
                    KW Only Arg Count: 0
                    Stack Size: 8
                    Flags: 0x00000013 (CO_OPTIMIZED | CO_NEWLOCALS | CO_NESTED)
                    [Names]
                        'KEY'
                        'len'
                    [Locals+Names]
                        '.0'
                        'i'
                        'f'
                    [Constants]
                        24
                        255
                        74
                    [Disassembly]
                        0       RESUME                        0
                        2       BUILD_LIST                    0
                        4       LOAD_FAST                     0: .0
                        6       FOR_ITER                      54 (to 116)
                        8       UNPACK_SEQUENCE               2
                        12      STORE_FAST                    1: i
                        14      STORE_FAST                    2: f
                        16      LOAD_FAST                     2: f
                        18      LOAD_CONST                    0: 24
                        20      BINARY_OP                     0 (+)
                        24      LOAD_CONST                    1: 255
                        26      BINARY_OP                     1 (&)
                        30      LOAD_GLOBAL                   0: KEY
                        42      LOAD_FAST                     1: i
                        44      LOAD_GLOBAL                   3: NULL + len
                        56      LOAD_GLOBAL                   0: KEY
                        68      PRECALL                       1
                        72      CALL                          1
                        82      BINARY_OP                     6 (%)
                        86      BINARY_SUBSCR
                        96      BINARY_OP                     12 (^)
                        100     LOAD_CONST                    2: 74
                        102     BINARY_OP                     10 (-)
                        106     LOAD_CONST                    1: 255
                        108     BINARY_OP                     1 (&)
                        112     LIST_APPEND                   2
                        114     JUMP_BACKWARD                 55
                        116     RETURN_VALUE
            [Disassembly]
                0       RESUME                        0
                2       LOAD_CONST                    1: <CODE> <listcomp>
                4       MAKE_FUNCTION                 0
                6       LOAD_GLOBAL                   1: NULL + enumerate
                18      LOAD_FAST                     0: flag
                20      PRECALL                       1
                24      CALL                          1
                34      GET_ITER
                36      PRECALL                       0
                40      CALL                          0
                50      RETURN_VALUE
        [Code]
            File Name: check.py
            Object Name: check
            Qualified Name: check
            Arg Count: 1
            Pos Only Arg Count: 0
            KW Only Arg Count: 0
            Stack Size: 3
            Flags: 0x00000003 (CO_OPTIMIZED | CO_NEWLOCALS)
            [Names]
                'transform'
                'CHECK'
            [Locals+Names]
                'flag'
            [Constants]
                None
            [Disassembly]
                0       RESUME                        0
                2       LOAD_GLOBAL                   1: NULL + transform
                14      LOAD_FAST                     0: flag
                16      PRECALL                       1
                20      CALL                          1
                30      LOAD_GLOBAL                   2: CHECK
                42      COMPARE_OP                    2 (==)
                48      RETURN_VALUE
        '__main__'
        '🎃 Welcome to SpookyCheck 🎃'
        '🎃 Enter your password for spooky evaluation 🎃'
        '👻 '
        "🦇 Well done, you're spookier than most! 🦇"
        '💀 Not spooky enough, please try again later 💀'
        None
    [Disassembly]
        0       RESUME                        0
        2       LOAD_CONST                    0: b'SUP3RS3CR3TK3Y'
        4       STORE_NAME                    0: KEY
        6       PUSH_NULL
        8       LOAD_NAME                     1: bytearray
        10      LOAD_CONST                    1: b'\xe9\xef\xc0V\x8d\x8a\x05\xbe\x8ek\xd9yX\x8b\x89\xd3\x8c\xfa\xdexu\xbe\xdf1\xde\xb6\\'
        12      PRECALL                       1
        16      CALL                          1
        26      STORE_NAME                    2: CHECK
        28      LOAD_CONST                    2: <CODE> transform
        30      MAKE_FUNCTION                 0
        32      STORE_NAME                    3: transform
        34      LOAD_CONST                    3: <CODE> check
        36      MAKE_FUNCTION                 0
        38      STORE_NAME                    4: check
        40      LOAD_NAME                     5: __name__
        42      LOAD_CONST                    4: '__main__'
        44      COMPARE_OP                    2 (==)
        50      POP_JUMP_FORWARD_IF_FALSE     88 (to 228)
        52      PUSH_NULL
        54      LOAD_NAME                     6: print
        56      LOAD_CONST                    5: '🎃 Welcome to SpookyCheck 🎃'
        58      PRECALL                       1
        62      CALL                          1
        72      POP_TOP
        74      PUSH_NULL
        76      LOAD_NAME                     6: print
        78      LOAD_CONST                    6: '🎃 Enter your password for spooky evaluation 🎃'
        80      PRECALL                       1
        84      CALL                          1
        94      POP_TOP
        96      PUSH_NULL
        98      LOAD_NAME                     7: input
        100     LOAD_CONST                    7: '👻 '
        102     PRECALL                       1
        106     CALL                          1
        116     STORE_NAME                    8: inp
        118     PUSH_NULL
        120     LOAD_NAME                     4: check
        122     LOAD_NAME                     8: inp
        124     LOAD_METHOD                   9: encode
        146     PRECALL                       0
        150     CALL                          0
        160     PRECALL                       1
        164     CALL                          1
        174     POP_JUMP_FORWARD_IF_FALSE     13 (to 202)
        176     PUSH_NULL
        178     LOAD_NAME                     6: print
        180     LOAD_CONST                    8: "🦇 Well done, you're spookier than most! 🦇"
        182     PRECALL                       1
        186     CALL                          1
        196     POP_TOP
        198     LOAD_CONST                    10: None
        200     RETURN_VALUE
        202     PUSH_NULL
        204     LOAD_NAME                     6: print
        206     LOAD_CONST                    9: '💀 Not spooky enough, please try again later 💀'
        208     PRECALL                       1
        212     CALL                          1
        222     POP_TOP
        224     LOAD_CONST                    10: None
        226     RETURN_VALUE
        228     LOAD_CONST                    10: None
        230     RETURN_VALUE

We can then generate the Python source-code from the byte-code:

KEY = b"SUP3RS3CR3TK3Y"
CHECK = b"\xe9\xef\xc0V\x8d\x8a\x05\xbe\x8ek\xd9yX\x8b\x89\xd3\x8c\xfa\xdexu\xbe\xdf1\xde\xb6\\"

def transform(flag):
    result = []
    for i, f in enumerate(flag):
        value = (f + 24) & 255
        value ^= KEY[i % len(KEY)]
        value -= 74
        value &= 255
        result.append(value)
    return result

def check(flag):
    return transform(flag) == CHECK

if __name__ == "__main__":
    print("🎃 Welcome to SpookyCheck 🎃")
    print("🎃 Enter your password for spooky evaluation 🎃")
    inp = input("👻 ")
    if check(inp.encode()):
        print("🦇 Well done, you're spookier than most! 🦇")
    else:
        print("💀 Not spooky enough, please try again later 💀")

The following script was then used to reverse the flag that basically does everything in reverse-order to the original transform() function.

KEY = b"SUP3RS3CR3TK3Y"
CHECK = b"\xe9\xef\xc0V\x8d\x8a\x05\xbe\x8ek\xd9yX\x8b\x89\xd3\x8c\xfa\xdexu\xbe\xdf1\xde\xb6\\"

def reverse_transform(result):
    reversed_flag = []
    for i, value in enumerate(result):
        value += 74
        value &= 255
        value ^= KEY[i % len(KEY)]
        value -= 24
        value &= 255
        reversed_flag.append(value)
    return bytes(reversed_flag)

# Reverse the flag using the transform function
reversed_flag = bytes(reverse_transform(CHECK))
print(reversed_flag.decode('utf-8'))
python3 check_rev.py
HTB{mod3rn_pyth0n_byt3c0d3}

Flag: HTB{mod3rn_pyth0n_byt3c0d3}