A brief analysis of some basic features of the Swift language

  • 2020-05-09 19:22:49
  • OfStack

Swift is apple's latest programming language, which many say "replaces" Objective-C. But there is no conclusive evidence. I spent some time reverse-engineering Swift2 and its running environment, and then I made a few discoveries about Swift. So far, the conclusion is that Swift is Objective-C without a messaging mechanism.

object

Believe it or not, objects in Swift are objects in Objective-C. In the Mach-O2 base file, s 18en_classlist contains the data for each class in the base 2 file. Its structure is as follows:
 


struct objc_class {
    uint64_t isa;
    uint64_t superclass;
    uint64_t cache;
    uint64_t vtable;
    uint64_t data;
};

(note: all structures are from the 64-bit version)

Notice the data record, which points to a structure in the class that lists methods, instance variables, protocols, and so on. Normally, data is 8-byte aligned, but for the Swift class, the last bit of data is just one byte.


class

The real structure of the Swift class is a bit odd. The Swift class does not have the Objective-C method. We will implement it later. The variables of the Swift class are stored as instance variables. What Swift's getter and setter methods really change is the value of the instance variable. The odd thing is that the instance variable of the swift class has no type encoding. Typically, the pointer to the type encoding should be NULL. This is probably due to the fact that the Objective-C runtime does not support handling the Swift variable itself.

inheritance

The Swift inheritance is what you would expect. In Swift, Square is a subclass of shape and a subclass of Objective-C Shape. However, there are no superclasses in the Swift class?

For example,

class Shape { }

In this example, the Shape class is a subclass of SwiftObject. SwiftObject is a root Objective-C class, similar to NSObject. It has no superclass, meaning isa points to itself. It is intended to replace the standard Objective-C runtime methods with Swift runtime methods such as allocation and deallocation. For example, (void)retain does not call objc_retain, but it does call swift_retain.

Class method

As I mentioned earlier, the class of the Swift object has no methods, instead of functions like C++, name changes and everything. This may be why Swift claims to be faster than Objective-C. You no longer need to find and call method implementations for objc_msgSend  .

In Objective-C, the method is implemented like this:

type method(id self, SEL _cmd, id arg1, id arg2, ...)

The Swift method is very similar, but slightly USES a different parameter arrangement, self is passed as the last parameter, and there is no selector.

type method(id arg1, id arg2, ..., id self)


Virtual table

Like C++1, the Swift class has a virtual table that lists the methods in the class. It is placed directly behind the class data in the base 2 file and looks like this:
 


struct swift_vtable_header {
    uint32_t vtable_size;
    uint32_t unknown_000;
    uint32_t unknown_001;
    uint32_t unknown_002;
    void* nominalTypeDescriptor;
    // vtable pointers
}

As far as I know, virtual tables in the Swift class are only used when they are visible at compile time. Otherwise, it will look like a mess of symbols.

After reforming

Swift keeps the metadata of functions in their respective symbols, which is called named renording. Metadata treasure trove names of functions (obvious), properties, module names, parameter types, return value types, and more, as in this example
 


class Shape{
    func numberOfSides() -> Int {
        return 5
    }
}

The renormalization name of the simpleDescription method is:

_TFC9swifttest5Shape17simpleDescriptionfS0_FT_Si. Here are the details:

_T - the prefix for all Swift symbols, starting with _T for each symbol.

F - function

C - class functions (methods)

9swifttest - module name with a length prefix

5Shape - the class to which the function belongs, with a length prefix

17simpleDescription - function name

f - function property. In this case it's f, which is a normal function.

S0_FT- I'm not exactly sure what this means, but it's a marker for the beginning of arguments and return types, right

'_' - the underscore separates the type of the parameter and the return value. Because the function takes no arguments, it follows directly after S0_FT

S - the beginning of the return value. 'S' stands for Swift; The return type is the built-in type of Swift, and the next character determines the type

i - this is the built-in type for Swift. A lowercase "I" represents Int.


Function attributes

Character types

f               ordinary functions

s               setter

g               getter

d               destructor

D               release

c               constructor

C             dispenser

Swift inner function

Character types

a               array

b               Boolean

c               character constants

d               double precision floating point number

f               single precision floating point

i               integer

u               UInt type

Q               is implicitly optional

S                  

In addition to functions, there are many naming conversion mechanisms, and I'll give you just a brief overview.

Hook function

Fed up with the semantics, let's touch on something interesting! Let's say we have a class that looks like this:
 


class Shape {
    var numberOfSides: Int;
 
    init(){
        numberOfSides = 5;
    }
}

For example, if we want to change the value of numberOfSides to 4, there are many ways to do this. We can use MobileSubstrate to hook into the getter method and change the return value like this:

 


int (*numberOfSides)(id self);
 
MSHook(int, numberOfSides, id self){
    return 4;
}
 
%ctor{
    numberOfSides = (int (*)(id self)) dlsym(RTLD_DEFAULT, "_TFC9swifttest5Shapeg13numberOfSidesSi");
    MSHookFunction(numberOfSides, MSHake(numberOfSides));
}

If we create an instance of a shape and print out the value of numberOfSides, we get 4! Looks good, doesn't it? Now, I know you're probably thinking, shouldn't we be returning 1 object instead of 4?


Well, in Swift, many of the built-in types are written. One Int, for example, and one int in the C language (although it could be a long shaping - don't let me get this). One small hint, String type is a bit old, this is a low priority UTF-16 string, so no literal of C can be used.

Let's do the same thing, but this time, we're not going to hook the reaper, we're going to hook the reaper.

 


void (*setNumberOfSides)(int newNumber, id self);
 
MSHook(void, setNumberOfSides, int newNumber, id self){
    _setNumberOfSides(4, self);
}
 
%ctor {
    setNumberOfSides = (void (*)(int newNumber, id self)) dlsym(RTLD_DEFAULT, "_TFC9swifttest5Shapes13numberOfSidesSi");
    MSHookFunction(setNumberOfSides, MSHake(setNumberOfSides));
}

One more try, and then... Or 5. What's the matter, you ask? Well, somewhere in Swift, functions are inlined. The class constructor is where one of them is. It sets numberOfSides directly to ivar, and the setter will only be called when the value is set again by the top-level code. It's called there, and you know what? We got a 4.

Finally, let's modify numberOfSides by setting the value of the instance variable directly.

 


void (*setNumberOfSides)(int newNumber, id self);
 
MSHook(void, setNumberOfSides, int newNumber, id self){
    MSHookIvar<int>(self, "numberOfSides") = 4;
}
 
%ctor {
    setNumberOfSides = (void (*)(int newNumber, id self)) dlsym(RTLD_DEFAULT, "_TFC9swifttest5Shapes13numberOfSidesSi");
    MSHookFunction(setNumberOfSides, MSHake(setNumberOfSides));
}

This function can be implemented, though it is not recommended, but it does work.

That's what I'm going to write about for now. Of course, there are many other things I'm looking at, including the witness table, which I don't know much about, so I can't write a summary here. Many of the things that have changed in this article are just the things I currently reverse engineer from running and viewing base 2 files compiled in the Swift language.

What I found should be pretty good, which means MobileSubstrate won't die with Objective-C1, and fine tuning can still be done! I wonder what the future of the app store will be like in a jailbreak scenario... Can logo be updated to use the auto-destruct name? Even handling common Swift types of libraries...

If you find out more about how Swift works, don't hesitate to let me know!


Related articles: