Description
Python says that it can pickle arbitrary objects, which is true, but you still need the source code for the original object wherever you unpickle it, if you want to be able to call methods on it/instantiate new objects of the same type/etc.
So, I've made a super pickler! This packages all of an object's code with the object itself, for easy transportation. At least, I hope ... I haven't actually written an superunpickler myself. Hopefully you can still get the flag!
Note: High point value is due to this being a final challenge, and is not necessarily an indicator of difficulty.
Attachments
https://imaginaryctf.org/f/5O8pS#superpickle.py
https://imaginaryctf.org/f/D3jso#pick
Writeup
Unpickling the provided pickle gives a dictionary containing the pickle of the initial object, and the marshalled code of all of its functions. You can unpickle the object if the tree.Tree class exists at all (class Tree: pass
in tree.py
in the same directory will suffice). You can set the __code__
attribute of each of the attributes on the object and the Tree class, and you've recreated the original object/class.
Now, you need to figure out how the tree was actually encoded. Using dir()
will show that there are left, right, and data fields. You can either disassemble the __init__
function and reverse it, or you can make another tree of the same length (using the builtin length method, or a custom one) with unique characters, and map the location of the characters in the new tree to the location of the characters in the flag tree.
I've done the second method here:
#!/usr/bin/env python3.9
import pickle
import marshal
from dis import dis
from copy import deepcopy
from tree import Tree
def unpick(b, clss):
d = pickle.loads(b)
obj = pickle.loads(d["__"])
for key in d:
if key != "__":
obj.__setattr__(key, lambda x:x)
obj.__getattribute__(key).__setattr__("__code__", marshal.loads(d[key]))
setattr(clss, key, lambda x,y:x)
setattr(getattr(clss, key), "__code__", marshal.loads(d[key]))
return obj
def tree_map(d, a, b):
if a.data == None:
return
d[a.data] = b.data
tree_map(d, a.left, b.left)
tree_map(d, a.right, b.right)
flag = unpick(open("pick", "rb").read(), Tree)
x = Tree(''.join(chr(i) for i in range(32,32+len(flag))))
d = {}
tree_map(d, x, flag)
out = ['~']*len(flag)
for key in d:
out[ord(key)-32] = d[key]
print(''.join(out))
Flag
ictf{didyouknowthatpicklesgrowontreeswellnowyoudo}