Swift names die in nm the moment I strip. ObjC selector strings do not, because they live in __TEXT,__objc_methname as C strings the runtime has to see. This lab is a self-signed LabVault binary with one Swift class, one method that stays pure Swift, and one method marked @objc so I can still find it after strip. cryptid=1 stops the lab. FairPlay unwrap, jailbreak, AMFI bypass: out of scope.

objc_msgSend dispatch
Figure 1. Only the @objc entry is reachable via objc_msgSend / methname. Pure Swift IMPs are $s… symbols.

Gate the image, then dump both name spaces

 1$ file LabVault
 2LabVault: Mach-O 64-bit executable arm64
 3
 4$ otool -l LabVault | egrep 'cmd LC_ENCRYPTION|cryptid|segname __TEXT|__objc_methname|LC_CODE'
 5      cmd LC_SEGMENT_64
 6  segname __TEXT
 7  sectname __objc_methname
 8      cmd LC_ENCRYPTION_INFO_64
 9  cryptid 0
10      cmd LC_CODE_SIGNATURE

cryptid 0. If it is 1, I stop.

Lab source (the only Swift I need):

 1import Foundation
 2
 3@objc(LabVault)
 4final class LabVault: NSObject {
 5    /// Pure Swift. Will vanish from nm after strip. Never in methname.
 6    func secretLength(_ token: String) -> Int {
 7        token.count
 8    }
 9
10    /// Exposed to ObjC. Selector startWithToken: survives strip.
11    @objc(startWithToken:)
12    func startWithToken(_ token: String) {
13        _ = secretLength(token)
14        insecureCopy(token)
15    }
16
17    func insecureCopy(_ token: String) {
18        var buf = [CChar](repeating: 0, count: 16)
19        token.withCString { src in
20            memcpy(&buf, src, strlen(src) + 1)   // lab bug
21        }
22        _scratch = buf[0]
23    }
24}

nm before strip, then swift demangle

 1$ nm LabVault | grep '$s8LabVault'
 20000000100003a40 T $s8LabVaultAAC12secretLengthySiSSF
 30000000100003b20 T $s8LabVaultAAC14startWithTokenyySSF
 40000000100003c00 t $s8LabVaultAAC14startWithTokenyySSFTo
 50000000100003d80 T $s8LabVaultAAC13insecureCopyyySSF
 60000000100003f00 T $s8LabVaultAACMa
 70000000100003f20 T $s8LabVaultAACN
 80000000100004000 S _OBJC_CLASS_$_LabVault
 9                 U _objc_msgSend
10                 U _memcpy
11                 U _strlen

$s is the Swift 5+ mangling prefix. To on the third symbol is the ObjC thunk (T = thunk, o = Objective-C): the thing objc_msgSend actually jumps to for -[LabVault startWithToken:]. Ma / N are type metadata / nominal type descriptor. _OBJC_CLASS_$_LabVault exists only because the class inherits NSObject and is @objc(LabVault).

1$ xcrun swift demangle \
2    '$s8LabVaultAAC12secretLengthySiSSF' \
3    '$s8LabVaultAAC14startWithTokenyySSF' \
4    '$s8LabVaultAAC14startWithTokenyySSFTo' \
5    '$s8LabVaultAAC13insecureCopyyySSF'
6$s8LabVaultAAC12secretLengthySiSSF     ---> LabVault.LabVault.secretLength(_:) -> Swift.Int
7$s8LabVaultAAC14startWithTokenyySSF    ---> LabVault.LabVault.startWithToken(_:) -> ()
8$s8LabVaultAAC14startWithTokenyySSFTo  ---> thunk for @objc LabVault.LabVault.startWithToken(_:) -> ()
9$s8LabVaultAAC13insecureCopyyySSF      ---> LabVault.LabVault.insecureCopy(_:) -> ()

Reading the mangling without the tool, once: $s Swift, 8LabVault module, AA class with the same name as the module, C class, then the identifier, then the type. yySSF is (String) -> () with an empty first label. ySiSSF is (String) -> Int. I still run swift demangle instead of hand-parsing when the name has substitutions (A, B, …) in the middle.

nm after strip vs __objc_methname

1$ strip LabVault -o LabVault.stripped
2$ file LabVault.stripped
3LabVault.stripped: Mach-O 64-bit executable arm64
4
5$ nm LabVault.stripped | grep '$s8LabVault'
6$ nm LabVault.stripped | grep secretLength
7$ nm LabVault.stripped | grep startWithToken
8$ nm LabVault.stripped | grep OBJC_CLASS
90000000100004000 S _OBJC_CLASS_$_LabVault

The $s… text symbols are gone. _OBJC_CLASS_$_LabVault can remain as an exported ObjC class symbol depending on strip flags; on this run it did. The selector string is not in nm at all — it is a cstring section:

1$ otool -v -s __TEXT __objc_methname LabVault.stripped | egrep 'start|secret|insecure|length'
2Contents of (__TEXT,__objc_methname) section
30000000100004a10  startWithToken:
40000000100004a21  .cxx_destruct
5$ # no secretLength:, no insecureCopy:

secretLength and insecureCopy were never @objc, so they were never in methname. Strip cannot remove startWithToken: without breaking objc_msgSend. That is the recovery rule: if I need a name after strip, it has to have crossed the ObjC boundary.

1$ class-dump LabVault.stripped | sed -n '/LabVault/,+12p'
2@interface LabVault : NSObject
3- (void)startWithToken:(id)token;
4@end

class-dump reads __objc_methname / class dumps. One method. The two pure-Swift methods are invisible here. That is not a failure of class-dump; that is the Swift ABI.

ARM64: thunk vs Swift IMP vs the lab copy

 1; otool -tV LabVault   (unstripped, file VA, slide 0)
 2; thunk: $s8LabVaultAAC14startWithTokenyySSFTo  == -[LabVault startWithToken:]
 30000000100003c00  pacibsp
 40000000100003c04  stp    x22, x21, [sp, #-0x30]!
 50000000100003c08  stp    x20, x19, [sp, #0x10]
 60000000100003c0c  stp    x29, x30, [sp, #0x20]
 70000000100003c10  add    x29, sp, #0x20
 80000000100003c14  mov    x19, x0               ; self
 90000000100003c18  mov    x20, x2               ; NSString * (ObjC arg)
100000000100003c1c  mov    x0, x20
110000000100003c20  bl     0x100005800           ; Swift bridge NSString → String
120000000100003c24  mov    x0, x19
130000000100003c28  ; x1/x2 now hold the Swift String value
140000000100003c2c  bl     0x100003b20           ; $s8LabVaultAAC14startWithTokenyySSF
150000000100003c30  ldp    x29, x30, [sp, #0x20]
160000000100003c34  ldp    x20, x19, [sp, #0x10]
170000000100003c38  ldp    x22, x21, [sp], #0x30
180000000100003c3c  retab

x0 = self, x1 = _cmd, x2 = NSString * at the thunk. After the bridge, the Swift IMP takes a Swift String (two registers, not an object pointer). That is why a Frida ObjC.Object(args[2]) only makes sense on the thunk, not on $s8LabVaultAAC14startWithTokenyySSF.

 1; $s8LabVaultAAC13insecureCopyyySSF
 20000000100003d80  pacibsp
 30000000100003d84  stp    x29, x30, [sp, #-0x40]!
 40000000100003d88  mov    x29, sp
 50000000100003d8c  sub    sp, sp, #0x10         ; 16-byte buf
 60000000100003d90  ; Swift String in x20 / x21
 70000000100003d94  bl     0x100005840           ; String.withCString
 8; ... callback:
 90000000100003de0  add    x0, x29, #0x20        ; &buf[16]
100000000100003de4  mov    x1, x19               ; const char *src
110000000100003de8  mov    x2, x8                ; strlen+1, unbounded
120000000100003dec  bl     0x100005900           ; _memcpy
AArch64 frame
Figure 2. Thunk frame saves the NSString; Swift IMP frame has the 16-byte buf under memcpy.
Mach-O load commands
Figure 3. methname is a __TEXT section. Strip removes the symbol table, not that section. cryptid=1 still stops the lab.

lldb: break on the selector, or on the mangled name

Unstripped:

1(lldb) breakpoint set -n '$s8LabVaultAAC14startWithTokenyySSF'
2(lldb) breakpoint set -n '-[LabVault startWithToken:]'    ; hits the To thunk

Stripped, the first command fails (no symbol). The second still works if the ObjC runtime can see the class — or I break on objc_msgSend and filter:

 1(lldb) process launch --stop-at-entry
 2(lldb) breakpoint set -n objc_msgSend
 3(lldb) command script import lldb_sel.py
 4; same helper as the objc_msgSend lab: stop only when sel == 'startWithToken:'
 5[msgSend] sel=startWithToken: self=0x0000000281a0c0c0
 6(lldb) po $x0
 7<LabVault: 0x281a0c0c0>
 8(lldb) po [$x2 length]
 936
10(lldb) # do not po the token
11(lldb) disassemble -s $pc -c 8

On the stripped binary I recover the thunk address from the live objc_msgSend stop (x0/x1 → class cache → IMP), not from nm.

1$ otool -v -s __TEXT __objc_methname LabVault.stripped | grep start
20000000100004a10  startWithToken:
3$ # file VA of the cstring; lldb will slide it

Sanitized reproduction

UI pastes lab_ + 'A'*32. I log length.

1(lldb) po [$x2 length]
236
3(lldb) memory read -c 8 (char *)[(NSString *)$x2 UTF8String]
40x0000000283bb4a00: 6c 61 62 5f 41 41 41 41    lab_AAAA
5# remaining 28 bytes not copied into the note

Crash on the unstripped build (symbols still in the report):

1* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2)
2    frame #0: 0x00000001890afc2c libsystem_platform.dylib`_platform_memmove + 204
3    frame #1: 0x0000000100003dec LabVault`$s8LabVaultAAC13insecureCopyyySSF + 0x6c
4    frame #2: 0x0000000100003b70 LabVault`$s8LabVaultAAC14startWithTokenyySSF + 0x50
5    frame #3: 0x0000000100003c2c LabVault`$s8LabVaultAAC14startWithTokenyySSFTo + 0x2c

Same crash on the stripped build (names gone from frames 1–2):

1    frame #1: 0x0000000100003dec LabVault` + 0x3dec
2    frame #2: 0x0000000100003b70 LabVault` + 0x3b70
3    frame #3: 0x0000000100003c2c LabVault`-[LabVault startWithToken:] + 0x2c

Frame 3 still has the ObjC name because the thunk is the IMP the runtime registered. Frames 1–2 are recovered from the dSYM with atos, or not at all if I also stripped the dSYM. That is the operational difference @objc makes in a crash report.

ASAN:

1==412==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x...
2WRITE of size 37 at ... thread T0
3    #0 memcpy
4    #1 $s8LabVaultAAC13insecureCopyyySSF LabVault.swift:24
5    #2 $s8LabVaultAAC14startWithTokenyySSF LabVault.swift:16
6Shadow bytes around the buggy address:
7  00 00 00 00[f1]f1 f1 f1 00 00[f3]f3

Repro is: 36-byte UTF-8, 16-byte Swift Array<CChar>, memcpy size 37, pc in insecureCopy, selector startWithToken: still in methname after strip. Not a decrypt, not a jailbreak.

Closing

$s… demangles while the symbol table exists. strip takes that away. @objc writes a selector into __objc_methname and a To thunk the runtime can call; class-dump and objc_msgSend breakpoints keep working. Pure Swift methods are recovered from a dSYM or not at all. cryptid=1 still ends the session before any of this.

Commands appendix

1otool -l LabVault | egrep 'cryptid|__objc_methname'
2nm LabVault | grep '$s8LabVault'
3xcrun swift demangle '$s8LabVaultAAC14startWithTokenyySSF'
4strip LabVault -o LabVault.stripped
5nm LabVault.stripped | grep '$s'
6otool -v -s __TEXT __objc_methname LabVault.stripped
7class-dump LabVault.stripped
8xcrun lldb ./LabVault