Function hooking in Swift

Dmitry Rodionov edited this page Jun 19, 2014 · 3 revisions
Clone this wiki locally

I spent the last two weeks being excited about the new things Apple showed us at WWDC’14. And the language Swift is one of the most interesting inventions. I'm not sure why do I need to learn it (because, hey, it even uses Objective-C runtime so why threw my C away?), but learning new stuff is usually cool, so why not?
My first „Hello, world!“ program was a port of rd_route() to Swift — since one day or another there will be lots of Swift applications and I want to be ready with all my tools.
The porting process wasn't straightforward, requiring some old plain C code and also some internal non–documented Swift stuff that you'll see below.

What Swift has not?

The only wall Apple engineers built for me was this: there isn't any way to obtain a pointer to a function in Swift:

Note that C function pointers are not imported in Swift.

(from „Using Swift with Cocoa and Objective-C“)

But how do we know what address to hook and where to jump in this case? Let's go deeper and take a little look what Swift's func sometimes is on a binary level.

When you pass a function as an argument of a generic type <T>, Swift doesn't pass its raw address directly, but a pointer to a trampoline function (see below) along with some meta information about the function. And the trampoline itself is a part of a structure wrapping the original function.


What does it mean?

Let's illustrate it with an example:

func call_function(f : () -> Int) {
    let b = f()
}

func someFunction() -> Int {
    return 0
}

In Swift we just write call_function(someFunction).
But rather than performing the call as call_function(&someFunction), Swift compiler produces the code

struct swift_func_wrapper *wrapper =  ... /* configure wrapper for someFunction() */
struct swift_func_type_metadata *type_metadata = ... /* information about function's arguments and return type */
call_function(wrapper->trampoline, type_metadata);

A wrapper has the following structure:

struct swift_func_wrapper {
    uint64_t **trampoline_ptr_ptr; // = &trampoline_ptr
    uint64_t *trampoline_ptr;
    struct swift_func_object *object;
}

And what is the swift_func_object type? To create this object, Swift runtime uses a global constant named metadata[N] (which is unique for each function call that takes your func as an argument of a generic type , so for this code:

func callf(f: () -> ()) {
    f();
}
callf(someFunction);
callf(someFunction);

two constants metadata and metadata2 will be created).

A metadata[N]’s structure is kinda this:

struct metadata {
    uint64_t *destructor_func;
    uint64_t *unknown0;
    const char type:1; // I'm not sure about this and padding,
    char padding[7];   // maybe it's just a uint64_t too...
    uint64_t *self; 
}

Initially metadataN has only two fields set: destructor_func and type. The first is a pointer to a function that will be used to deallocate all the memory for an object created with swift_allocObject(). And the latter is the object's type identifer (0x40 or '@' for functions/methods), and is (somehow) used by swift_allocObject() to create a right object for our func:

swift_allocObject(&metadata2->type, 0x20, 0x7);

Once the func object is created it has the following structure:

struct swift_func_object {
    uint64_t *original_type_ptr;
    uint64_t *unknown0;
    uint64_t function_address;
    uint64_t *self;
}

The first field is a pointer to a corresponding metadata[N]->type value, the second one seems to be 0x4 | 1 << 24 (0x100000004) and that's indicates something maybe (dunno what). function_address is what we actually interested in for hooking, and self is (suddenly) a pointer to the self (if our object represents a plain function this field is NULL).

OK, so what about trampolines I've started the paragraph with? Actually I don't get why does Swift runtime need them (yet), but anyway here is what they look like in a wild:

void* someFunction_Trampoline(void *unknown, void *arg, struct swift_func_object *desc)
{
    void* target_function = (void *)desc->function_address;
    uint64_t *self = desc->self;

    swift_retain_noresult(desc->self); // yeah, retaining self is cool!
    swift_release(desc);

    _swift_Trampoline(unknown, arg, target_function, self);
    return unknown;
}

void *_swift_Trampoline(void *unknown, void *arg, void *target_function, void *self)
{
    target_function(arg, self);
    return unknown;
}

Let's build it

Imagine you have these functions in your Swift code:

func takesFunc<T>(f : T) {
    ...
}
func someFunction() {
    ...
}

and you want to compose them like this

    takesFunc(someFunction)

This one line of code transforms into pretty big C thing:

struct swift_func_wrapper *wrapper = malloc(sizeof(*wrapper));
wrapper->trampoline_ptr     = &someFunction_Trampoline;
wrapper->trampoline_ptr_ptr = &(wrapper.trampoline);
wrapper->object = ({
    // let's say the metadata for this function is `metadata2`
    struct swift_func_object *object = swift_allocObject(&metadata2->type, 0x20, 0x7);
    object->function_address = &someFunction;
    object->self = NULL;
    object;
}); 


// global constant for the type of someFunction's arguments
const void *arg_type = &kSomeFunctionArgumentsTypeDescription;
// global constant for the return type of someFunction
const void *return_type = &kSomeFunctionReturnTypeDescription;

struct swift_func_type_metadata *type_metadata = swift_getFunctionTypeMetadata(arg_type, return_type);

takesFunc(wrapper->trampoline_ptr, type_metadata);

The swift_func_type_metadata struct is pretty opaque, so I don't have much to say about it (yet).


Back to the function pointers

Since we already know how functions are represented as arguments of a generic type, let's use it for you goal: to obtain a real pointer of a function!

The only thing we need to do is to note that we already have an address of trampoline_ptr field passed as the first argument, so the offset for the object field is just 0x8. Everything else is really easy to make up:

uint64_t _rd_get_func_impl(void *trampoline_ptr)
{
    struct swift_func_object *obj = (struct swift_func_object *)*(uint64_t *)(trampoline_ptr + 0x8);

    return obj->function_address;
}

Looks like it's time to write

rd_route(
    _rd_get_func_impl(firstFunction),
    _rd_get_func_impl(secondFunction),
    nil
)

but how do we call these C function from Swift?

For this purpose we'll use Swift undocumented feature: @asmname attibute that allows us to provide a Swift interface for our C functions. Usage:

@asmname("_rd_get_func_impl")
    func rd_get_func_impl<Q>(Q) -> UInt64;  

@asmname("rd_route")
    func rd_route(UInt64, UInt64, CMutablePointer<UInt64>) -> CInt;

That's all we need to use rd_route() in Swift!

But it can not handle any functions!

That is, you can not hook any function with generic typed arguments with rd_route() (it may be some bug in Swift or it may be not, I didn't figure it out yet). But you can override them easily using Swift's extensions, directly specifying types for the arguments:

class DemoClass {
    class func template <T : CVarArg>(arg : T, _ num: Int) -> String {
        return "\(arg) and \(num)";
    }
}

DemoClass.template("Test", 5) // "Test and 5"

extension DemoClass {
    class func template(arg : String, _ num: Int) -> String {
        return "{String}";
    }
    class func template(arg : Int, _ num: Int) -> String {
        return "{Int}";
    }
}

-- Your extension's methods for String and Int will be preferred over the original ones */
DemoClass.template("Test", 5) -- "{String}"
DemoClass.template(42, 5) -- "{Int}"
-- But for other types `template(T, Int)` will be used
DemoClass.template(["Array", "Item"], 5) --- "[Array, Item] and 5"

SWRoute

To make it easy to hook functions in Swift I've created a little wrapper called SWRoute — it's just a tiny class + one C function (_rd_get_func_impl() that we wrote before):

class SwiftRoute {
    class func replace<MethodT>(function targetMethod : MethodT, with replacement : MethodT) -> Int
    {
        return Int(rd_route(rd_get_func_impl(targetMethod), rd_get_func_impl(replacement), nil));
    }
}

Note that we get type-checking for free because Swift requires targetMethod and replacement to be the same — MethodT — type (^^,)
Also there isn't any way we can work with a copied original implementation, so I just pass nil as a third parameter for rd_route(). If you have some thoughts on how to integrate this pointer into Swift code — let me know!

You can find a lot of usage example for SWRoute at the repository.

And that's all!
(^^,)