Swift Class Disassembly
In a previous article, we looked at the disassembly of class creation and program semantics for a simple program that prints a string. It did expose how classes are defined and how objects are reference counted however, as simple as that example was. Now we're going to look at an equivalent Swift program
This swift program is equivalent to the Objective-C program, right down to the method names:
import Foundation
class Printer {
var str_to_print: String
init() {
str_to_print = ""
}
func printMsg() {
print(str_to_print)
}
func printString(message: String) {
str_to_print = message
printMsg()
}
}
let printer = Printer()
printer.printString(message: "Hello World!")
Now, when compiled, this will create a main subroutine that looks like this:
_main:
push rbp
mov rbp, rsp
push r13
sub rsp, 0x28
mov dword [rbp+var_C], edi
mov qword [rbp+var_18], rsi
call __T09swift_cmd7PrinterCMa
mov r13, rax
call __T09swift_cmd7PrinterCACycfC
lea rdi, qword [aHelloWorld]
mov ecx, 0xc
mov esi, ecx
mov edx, 0x1
mov qword [__T09swift_cmd7printerAA7PrinterCv], rax
mov rax, qword [__T09swift_cmd7printerAA7PrinterCv]
mov r13, qword [rax]
mov r13, qword [r13+0x78]
mov qword [rbp+var_20], rax
mov qword [rbp+var_28], r13
call __T0S2SBp21_builtinStringLiteral_Bw17utf8CodeUnitCountBi1_7isASCIItcfC
mov rdi, rax
mov rsi, rdx
mov rdx, rcx
mov r13, qword [rbp+var_20]
mov rax, qword [rbp+var_28]
call rax
xor eax, eax
add rsp, 0x28
pop r13
pop rbp
ret
First, notice that the Swift code uses call semantics and not message passing. The functions themselves are decorated, if you'd like to undecorate them you can use the swift-demangle
command (i.e. $ xcrun swift-demangle <mangled string>). This has some profound implications - first and foremost, call semantics make message interception, as implemented in Objective-C, impossible. We also have very distinct name mangling going on, all with the string "swift" incorporated into the defined functions. This is actually very useful! It makes determining the provenance of a given program trivial. If you run the command strings
over a program and grep for Swift, and you see these kinds of function names, you know that you're out of luck if you're planning on injecting anything into the program the old-fashioned way, using Objective-C tools.
Also, notice the last call:
call rax
This should be the call to the printer.printString(.)
method. Notice it's a dynamic call, where the address was previously moved into the RAX register.
mov rax, qword [__T09swift_cmd7printerAA7PrinterCv]
...
mov r13, qword [rax]
mov r13, qword [r13+0x78]
...
mov qword [rbp+var_28], r13
...
mov r13, qword [rbp+var_20]
mov rax, qword [rbp+var_28]
call rax
In this code, var_28 is -40. So here, we're executing a dynamic call into a function located on the stack, at an offset of -0x40. I've outlined the relevant code here. If you look over the code, we're essentially executing a call into an offset into the Swift object we've just created.
We've started to touch on how classes are allocated in Swift, and how calls into Swift objects are actually implemented. We'll start to look into that next.