Skip to content

Instantly share code, notes, and snippets.

@leptos-null
Last active August 5, 2025 23:16
Show Gist options
  • Save leptos-null/1f7fc9de9f7ae018f149f13446f9c8ae to your computer and use it in GitHub Desktop.
Save leptos-null/1f7fc9de9f7ae018f149f13446f9c8ae to your computer and use it in GitHub Desktop.

Revisions

  1. leptos-null revised this gist Aug 31, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion swift-hook.md
    Original file line number Diff line number Diff line change
    @@ -116,7 +116,7 @@ We'll look at this line by line.
    ```

    This is the Swift function in `Tweak.swift`. Note that we do not place the leading underscore (`_`) in this declaration.
    We could use `dlsym` or `MSFindAddress`, however since we're able to link against this symbol, I prefer to do that.
    We could use `dlsym` or `MSFindSymbol`, however since we're able to link against this symbol, I prefer to do that.
    This line is simply declaring that a function with this symbol name exists.
    Since this is C, the value of this function is a pointer to the code for the function.

  2. leptos-null revised this gist Aug 24, 2024. No changes.
  3. leptos-null revised this gist Aug 24, 2024. 1 changed file with 10 additions and 8 deletions.
    18 changes: 10 additions & 8 deletions swift-hook.md
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,7 @@ Thanks to help from [@NightwindDev](https://github.com/NightwindDev) for discuss

    Overall, the idea is simple:
    Write our own Swift code that will have the same calling convention as the target code,
    then get a pointer to our own code and the target function, and call `MSHookFunction` with
    then get a pointer to our own code and the target code, and call `MSHookFunction` with
    these values.

    ### Code
    @@ -37,7 +37,7 @@ This particular code is in an app called "SwizzleSandbox".

    Let's say that we want to hook the `greet(name:)` function.

    The first thing we'll do is create a Swift file in our tweak. I'll name it `Tweak.swift`.
    The first thing we'll do is create a Swift file in our tweak. I'll name this file `Tweak.swift`.

    We want to match the original source as much as possible, so we'll create a `struct` matching
    the original declaration, and a matching instance method:
    @@ -118,8 +118,7 @@ We'll look at this line by line.
    This is the Swift function in `Tweak.swift`. Note that we do not place the leading underscore (`_`) in this declaration.
    We could use `dlsym` or `MSFindAddress`, however since we're able to link against this symbol, I prefer to do that.
    This line is simply declaring that a function with this symbol name exists.
    We're also able to get the address of this symbol, which we'll use later.

    Since this is C, the value of this function is a pointer to the code for the function.

    ```c
    void *const target = dlsym(RTLD_MAIN_ONLY, "$s14SwizzleSandbox11ContentViewV5greet4nameS2S_tF");
    @@ -133,16 +132,17 @@ This time, we are using `dlsym`, since we cannot link against the application we
    assert(target != NULL);
    ```
    I check that the `target` function is found to make sure the symbol name is correct.
    I check that the `target` function is found to make sure the symbol name is correct,
    since this won't be checked at link time, as the previous technique is.
    You may want to change this behavior, once you've verified a particular hook.
    ```c
    MSHookFunction(target, $s5Tweak11ContentViewV10greet_hook4nameS2S_tF, NULL);
    ```

    This is straight-forward: Replace the implementation of `target` with the implementation of `$s5Tweak11ContentViewV10greet_hook4nameS2S_tF`.
    This code passes `NULL` to the last parameter, which is the "original" function pointer. Currently, we don't need the original function,
    however we'll cover that next.
    This code passes `NULL` to the last parameter, which is the "original" function pointer.
    Currently, we don't need the original function, however we'll cover that next.

    ### Calling orig

    @@ -185,7 +185,9 @@ we replace our "orig" function with `next`.

    We have to take this approach because the original implementation is expected to be called with the
    Swift calling convention. As far as I know, Swift does not allow a function with the Swift
    calling convention to be stored in a variable.
    calling convention to be stored in a variable. This is the reason we created another
    Swift function earlier: we replace the implementation of that new `_orig` function
    with the actual original implementation.

    We can now use `greet_orig(name:)` wherever we would like in our Swift code:

  4. leptos-null revised this gist Aug 23, 2024. 1 changed file with 72 additions and 9 deletions.
    81 changes: 72 additions & 9 deletions swift-hook.md
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,11 @@ This article aims to describe how to hook Swift functions.

    Thanks to help from [@NightwindDev](https://github.com/NightwindDev) for discussion and testing.

    Overall, the idea is simple:
    Write our own Swift code that will have the same calling convention as the target code,
    then get a pointer to our own code and the target function, and call `MSHookFunction` with
    these values.

    ### Code

    For simplicity, let's say that we have the following code in the application we're hooking:
    @@ -28,13 +33,14 @@ public struct ContentView: View {
    }
    ```

    This particular code is in an app called SwizzleSandbox.

    The first thing we'll do is create a Swift file in our tweak. I'll name it `Tweak.swift`.
    This particular code is in an app called "SwizzleSandbox".

    Let's say that we want to hook the `greet(name:)` function.

    We want to match the original source as much as possible, so we'll create a `struct` as well:
    The first thing we'll do is create a Swift file in our tweak. I'll name it `Tweak.swift`.

    We want to match the original source as much as possible, so we'll create a `struct` matching
    the original declaration, and a matching instance method:

    ```swift
    import SwiftUI
    @@ -78,17 +84,20 @@ If you get multiple results, or just want to see what these strings mean, pipe t

    ```bash
    printf '_$s5Tweak11ContentViewV10greet_hook4nameS2S_tF' | swift demangle
    # Tweak.ContentView.greet_hook(name: Swift.String) -> Swift.String
    ```

    ```swift
    Tweak.ContentView.greet_hook(name: Swift.String) -> Swift.String
    ```

    Now that we have our symbols, we just want to hook the Swift function like a normal C function.

    We'll create a Logos file. I'll call this file `Hooks.x`.
    We'll create a Logos file- I'll call this file `Hooks.x`.

    ```objc
    #include <assert.h>
    #include <dlfcn.h>
    #include <substrate.h>
    #import <assert.h>
    #import <dlfcn.h>
    #import <substrate.h>

    %ctor {
    void $s5Tweak11ContentViewV10greet_hook4nameS2S_tF(void);
    @@ -191,3 +200,57 @@ public struct ContentView {
    }
    }
    ```

    ### Hooking system code

    This technique works the same way for system code.

    In this demo, I'll hook `SwiftUI.Color.orange`, which has the following declaration:

    ```swift
    extension Color {
    public static let orange: Color
    }
    ```

    In `Tweak.swift`, I'll add this code:

    ```swift
    extension Color {
    public static var orange_hook: Color {
    .teal
    }
    }
    ```

    To get the symbol name from the SDK, I use

    ```bash
    grep 'Color.*orange' $(xcrun --show-sdk-path)/System/Library/Frameworks/SwiftUI.framework/SwiftUI.tbd
    ```

    ```
    _$s7SwiftUI5ColorV6orangeACvgZ
    ```

    And the usual for our own symbol:

    ```bash
    nm .theos/obj/Tweak.dylib | grep 'Color.*orange_hook'
    ```

    ```
    _$s7SwiftUI5ColorV5TweakE11orange_hookACvgZ
    ```

    We put this together in the same way as above, in `Hooks.x`:

    ```c
    void $s7SwiftUI5ColorV5TweakE11orange_hookACvgZ(void);
    void $s7SwiftUI5ColorV6orangeACvgZ(void);

    MSHookFunction($s7SwiftUI5ColorV6orangeACvgZ, $s7SwiftUI5ColorV5TweakE11orange_hookACvgZ, NULL);
    ```
    Since we can link against the symbol that we want to hook, I chose to do that instead of using
    `dlsym`.
  5. leptos-null created this gist Aug 23, 2024.
    193 changes: 193 additions & 0 deletions swift-hook.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,193 @@
    This article aims to describe how to hook Swift functions.

    Thanks to help from [@NightwindDev](https://github.com/NightwindDev) for discussion and testing.

    ### Code

    For simplicity, let's say that we have the following code in the application we're hooking:

    ```swift
    import SwiftUI

    public struct ContentView: View {

    public func greet(name: String) -> String {
    "Hello \(name)"
    }

    public var body: some View {
    VStack {
    Image(systemName: "globe")
    .imageScale(.large)
    .foregroundStyle(Color.orange)

    Text(greet(name: "Nightwind"))
    }
    .padding()
    }
    }
    ```

    This particular code is in an app called SwizzleSandbox.

    The first thing we'll do is create a Swift file in our tweak. I'll name it `Tweak.swift`.

    Let's say that we want to hook the `greet(name:)` function.

    We want to match the original source as much as possible, so we'll create a `struct` as well:

    ```swift
    import SwiftUI

    public struct ContentView {
    public func greet_hook(name: String) -> String {
    "Hi \(name)"
    }
    }
    ```

    Next, we need to get the symbol names for the original function and our replacement function.

    To get the name for the original function, I'll simply use `nm` with `grep`:

    ```bash
    nm SwizzleSandbox.app/SwizzleSandbox | grep 'ContentView.*greet'
    ```

    `nm` lists the symbol names in a binary. The `grep` parameter is `ContentView` for the name of the enclosing type,
    and then `greet` is the leading portion of the function name. `.*` matches any characters in-between.
    This command prints 1 result for me:

    ```
    _$s14SwizzleSandbox11ContentViewV5greet4nameS2S_tF
    ```

    We'll use the same technique for our own tweak:

    ```bash
    nm .theos/obj/Tweak.dylib | grep 'ContentView.*greet'
    ```

    This also prints 1 result:

    ```
    _$s5Tweak11ContentViewV10greet_hook4nameS2S_tF
    ```

    If you get multiple results, or just want to see what these strings mean, pipe them into `swift demangle`:

    ```bash
    printf '_$s5Tweak11ContentViewV10greet_hook4nameS2S_tF' | swift demangle
    # Tweak.ContentView.greet_hook(name: Swift.String) -> Swift.String
    ```

    Now that we have our symbols, we just want to hook the Swift function like a normal C function.

    We'll create a Logos file. I'll call this file `Hooks.x`.

    ```objc
    #include <assert.h>
    #include <dlfcn.h>
    #include <substrate.h>

    %ctor {
    void $s5Tweak11ContentViewV10greet_hook4nameS2S_tF(void);

    void *const target = dlsym(RTLD_MAIN_ONLY, "$s14SwizzleSandbox11ContentViewV5greet4nameS2S_tF");
    assert(target != NULL);

    MSHookFunction(target, $s5Tweak11ContentViewV10greet_hook4nameS2S_tF, NULL);
    }
    ```

    We'll look at this line by line.

    ```c
    void $s5Tweak11ContentViewV10greet_hook4nameS2S_tF(void);
    ```

    This is the Swift function in `Tweak.swift`. Note that we do not place the leading underscore (`_`) in this declaration.
    We could use `dlsym` or `MSFindAddress`, however since we're able to link against this symbol, I prefer to do that.
    This line is simply declaring that a function with this symbol name exists.
    We're also able to get the address of this symbol, which we'll use later.


    ```c
    void *const target = dlsym(RTLD_MAIN_ONLY, "$s14SwizzleSandbox11ContentViewV5greet4nameS2S_tF");
    ```

    On this line, we're doing the same thing - getting the address of the symbol we're looking for in the application we're hooking.
    Same as in the previous line, we do not put the leading underscore in the string.
    This time, we are using `dlsym`, since we cannot link against the application we're hooking.

    ```c
    assert(target != NULL);
    ```
    I check that the `target` function is found to make sure the symbol name is correct.
    You may want to change this behavior, once you've verified a particular hook.
    ```c
    MSHookFunction(target, $s5Tweak11ContentViewV10greet_hook4nameS2S_tF, NULL);
    ```

    This is straight-forward: Replace the implementation of `target` with the implementation of `$s5Tweak11ContentViewV10greet_hook4nameS2S_tF`.
    This code passes `NULL` to the last parameter, which is the "original" function pointer. Currently, we don't need the original function,
    however we'll cover that next.

    ### Calling orig

    To support calling the original implementation of a Swift function that we've hooked, we'll start by adding another function with a similar signature:

    ```swift
    public func greet_orig(name: String) -> String {
    fatalError("This implementation should never be called")
    }
    ```

    We get the symbol name the same way as before:

    ```bash
    nm .theos/obj/Tweak.dylib | grep 'ContentView.*greet_orig'
    ```

    ```
    _$s5Tweak11ContentViewV10greet_orig4nameS2S_tF
    ```

    We then bring this symbol into `Hooks.x`:

    ```c
    %ctor {
    void $s5Tweak11ContentViewV10greet_hook4nameS2S_tF(void);
    void $s5Tweak11ContentViewV10greet_orig4nameS2S_tF(void);

    void *const target = dlsym(RTLD_MAIN_ONLY, "$s14SwizzleSandbox11ContentViewV5greet4nameS2S_tF");
    assert(target != NULL);

    void *next;
    MSHookFunction(target, $s5Tweak11ContentViewV10greet_hook4nameS2S_tF, &next);
    MSHookFunction($s5Tweak11ContentViewV10greet_orig4nameS2S_tF, next, NULL);
    }
    ```

    In this snippet, we first store the address of the original implementation in `next`, then
    we replace our "orig" function with `next`.

    We have to take this approach because the original implementation is expected to be called with the
    Swift calling convention. As far as I know, Swift does not allow a function with the Swift
    calling convention to be stored in a variable.

    We can now use `greet_orig(name:)` wherever we would like in our Swift code:

    ```swift
    public struct ContentView {
    public func greet_orig(name: String) -> String {
    fatalError("This implementation should never be called")
    }

    public func greet_hook(name: String) -> String {
    greet_orig(name: name) + "!"
    }
    }
    ```