pickle_func(func, *args, target=None, protocol=None, b64encode=None)

    Encode a function in such a way that when it's unpickled, the function is
    reconstructed and called with the given arguments.

    Note:
        Compatibility between python versions is not guaranteed. Depending on
        the `target` python version, the opcodes of the provided function are
        transcribed to try to maintain compatibility. If an opcode is emitted
        which is not supported by the target python version, a KeyError will
        be raised.

        Constructs that are known to be problematic:

        - Python 2.6 and 2.7/3.0 use very different, incompatible opcodes for
          conditional jumps (if, while, etc). Serializing those is not
          always possible between python 2.6 and 2.7/3.0.

        - Exception handling uses different, incompatible opcodes between
          python 2 and 3.

        - Python 2 and python 3 handle nested functions very differently: the
          same opcode is used in a different way and leads to a crash. Avoid
          nesting functions if you want to pickle across python functions.

    Arguments:
        func(callable): The function to serialize and call when unpickled.
        args(tuple): The arguments to call the callable with.
        target: The internals description of the targeted python
            version. If this is ``None`` the specification of the currently
            running python version will be used.
        protocol(int): The pickle protocol version to use.
        b64encode(bool): Whether to base64 certain code object fields. Required
            when you prepare a pickle for python 3 on python 2. If it's
            ``None`` it defaults to ``False`` unless pickling from python 2 to
            python 3.

    Returns:
        bytes: The data that when unpickled calls ``func(*args)``.

    Example:
        >>> from pwny import *
        >>> import pickle
        >>> def hello(arg):
        ...     print('Hello, %s!' % arg)
        ...
        >>> p = pickle_func(hello, 'world')
        >>> del hello
        >>> pickle.loads(p)
        Hello, world!

def pickle_func(func, target=None, protocol=None, b64encode=None, *args):
    """pickle_func(func, *args, target=None, protocol=None, b64encode=None)

    Encode a function in such a way that when it's unpickled, the function is
    reconstructed and called with the given arguments.

    Note:
        Compatibility between python versions is not guaranteed. Depending on
        the `target` python version, the opcodes of the provided function are
        transcribed to try to maintain compatibility. If an opcode is emitted
        which is not supported by the target python version, a KeyError will
        be raised.

        Constructs that are known to be problematic:

        - Python 2.6 and 2.7/3.0 use very different, incompatible opcodes for
          conditional jumps (if, while, etc). Serializing those is not
          always possible between python 2.6 and 2.7/3.0.

        - Exception handling uses different, incompatible opcodes between
          python 2 and 3.

        - Python 2 and python 3 handle nested functions very differently: the
          same opcode is used in a different way and leads to a crash. Avoid
          nesting functions if you want to pickle across python functions.

    Arguments:
        func(callable): The function to serialize and call when unpickled.
        args(tuple): The arguments to call the callable with.
        target: The internals description of the targeted python
            version. If this is ``None`` the specification of the currently
            running python version will be used.
        protocol(int): The pickle protocol version to use.
        b64encode(bool): Whether to base64 certain code object fields. Required
            when you prepare a pickle for python 3 on python 2. If it's
            ``None`` it defaults to ``False`` unless pickling from python 2 to
            python 3.

    Returns:
        bytes: The data that when unpickled calls ``func(*args)``.

    Example:
        >>> from pwny import *
        >>> import pickle
        >>> def hello(arg):
        ...     print('Hello, %s!' % arg)
        ...
        >>> p = pickle_func(hello, 'world')
        >>> del hello
        >>> pickle.loads(p)
        Hello, world!
    """

    target = get_py_internals(target)

    code = six.get_function_code(func)
    code_obj = pwnypack.bytecode.CodeObject.from_code(code)
    translate_opcodes(code_obj, target)

    def code_reduce_v2(_):
        if b64encode:
            # b64encode co_code and co_lnotab as they contain 8bit data.
            co_code = PickleInvoke(base64.b64decode, base64.b64encode(code_obj.co_code))
            co_lnotab = PickleInvoke(base64.b64decode, base64.b64encode(code_obj.co_lnotab))
        else:
            co_code = code_obj.co_code
            co_lnotab = code_obj.co_lnotab

        if six.PY3:
            # Encode unicode to bytes as python 2 doesn't support unicode identifiers.
            co_names = tuple(n.encode('ascii') for n in code_obj.co_names)
            co_varnames = tuple(n.encode('ascii') for n in code_obj.co_varnames)
            co_filename = code_obj.co_filename.encode('ascii')
            co_name = code_obj.co_name.encode('ascii')
        else:
            co_names = code_obj.co_names
            co_varnames = code_obj.co_varnames
            co_filename = code_obj.co_filename
            co_name = code_obj.co_name

        return types.CodeType, (code_obj.co_argcount, code_obj.co_nlocals, code_obj.co_stacksize, code_obj.co_flags,
                                co_code, code_obj.co_consts, co_names, co_varnames, co_filename, co_name,
                                code_obj.co_firstlineno, co_lnotab)

    def code_reduce_v3(_):
        if b64encode:
            # b64encode co_code and co_lnotab as they contain 8bit data.
            co_code = PickleInvoke(base64.b64decode, base64.b64encode(code_obj.co_code))
            co_lnotab = PickleInvoke(base64.b64decode, base64.b64encode(code_obj.co_lnotab))
        else:
            co_code = code_obj.co_code
            co_lnotab = code_obj.co_lnotab

        return types.CodeType, (code_obj.co_argcount, code_obj.co_kwonlyargcount, code_obj.co_nlocals,
                                code_obj.co_stacksize, code_obj.co_flags, co_code, code_obj.co_consts,
                                code_obj.co_names, code_obj.co_varnames, code_obj.co_filename, code_obj.co_name,
                                code_obj.co_firstlineno, co_lnotab)

    # Stubs to trick cPickle into pickling calls to CodeType/FunctionType.
    class CodeType(object):  # pragma: no cover
        pass
    CodeType.__module__ = 'types'
    CodeType.__qualname__ = 'CodeType'

    class FunctionType(object):  # pragma: no cover
        pass
    FunctionType.__module__ = 'types'
    FunctionType.__qualname__ = 'FunctionType'

    protocol = get_protocol_version(protocol, target)

    old_code_reduce = copyreg.dispatch_table.pop(types.CodeType, None)
    if target['version'] < 300:
        copyreg.pickle(types.CodeType, code_reduce_v2)
    else:
        if six.PY2:
            if b64encode is False:
                warnings.warn('Enabling b64encode, pickling from python 2 to 3.')
            b64encode = True
        copyreg.pickle(types.CodeType, code_reduce_v3)

    # This has an astonishing level of evil just to convince pickle to pickle CodeType and FunctionType:
    old_code_type, types.CodeType = types.CodeType, CodeType
    old_function_type, types.FunctionType = types.FunctionType, FunctionType

    try:
        build_func = PickleInvoke(types.FunctionType, code, PickleInvoke(globals))
        return cPickle.dumps(PickleInvoke(build_func, *args), protocol)
    finally:
        types.CodeType = old_code_type
        types.FunctionType = old_function_type

        if old_code_reduce is not None:
            copyreg.pickle(types.CodeType, old_code_reduce)
        else:
            del copyreg.dispatch_table[types.CodeType]