SuperAPI AI Directory

re-swift

General & other

View repository

Updated 10-15 23:40

About this skill

Swift Reverse Engineering

When to use / when not to use

  • Use: Swift artifacts (Mach-O/ELF containing Swift mangling features with the $s prefix, or the __swift5_* reflection section), when symbol/protocol conformance/closure structure needs to be recovered
  • Use: distinguishing the Swift layer from the ObjC/C layer in mixed artifacts (follow the ObjC path first, then enter the Swift layer)
  • Do not use: pure ObjC (follow the ObjC path of [[re-ios]] / [[re-macos]]); pure C/C++ (follow [[re-cpp-abi]])
  • Do not use: when only ordinary decompilation is needed ([[re-ghidra]] / [[re-ida]] directly)

Tool preparation

swift-demangle (Swift toolchain, mangling decoding)

  • macOS: bundled with Xcode (xcrun swift-demangle); Linux: official Swift toolchain (download the tar package from swift.org and extract it, ready to use, includes swift-demangle); Windows: official Swift toolchain
  • Verify: echo '$s4main6myFuncyyF' | swift-demangle(on macOS use xcrun swift-demangle; with no arguments it reads stdin, outputs $s... ---> main.myFunc() -> ())
  • Batch: llvm-nm sample | swift-demangle;-simplified outputs simplified names;-expand outputs the decoding tree

swiftc / swift (optional, for comparison compilation)

  • macOS: bundled with Xcode; Linux/Windows: the same official toolchain as above (includes the compiler, so you can locally compile samples of the same version to compare symbol forms)
  • Verify: swift --version

llvm-nm / llvm-objdump (symbols and sections)

  • For installation and verification see [[re-cpp-abi]] tool preparation; on the Mach-O side otool is additionally required (see [[re-format-macho]] tool preparation)
  • Purpose: llvm-nm -m sample to see symbols and section attribution;otool -l sample | grep swift5 to locate the reflection section

Ghidra / IDA (decompilation base)

  • For installation and verification see [[re-ghidra]] / [[re-ida]]; Swift symbol decoding results are used for batch renaming (Ghidra can be scripted: write demangle output back to the symbol table, and the decompilation view restores readable function names)

Operating steps

Execute in order, archiving the output of each step (path + sha256, see [[re-triage]]).

  1. Artifact identification:

    strings -a sample | grep -E '^\$s|^_T0|^_\$S' | head
    llvm-nm sample 2>/dev/null | grep -E 'swift_|^\$s' | head   # ELF 用 readelf -s 同效果
    otool -l sample 2>/dev/null | grep -c swift5               # Mach-O 反射段存在性
    
    • $s(Swift 5+)、$S(Swift 4.2)、_T0(Swift 4.0) is the third-generation mangling prefix;swift_* runtime imports (swift_retain / swift_release / swift_allocObject) confirm the runtime
    • Pure ObjC has none of the above features → switch to [[re-ios]] / [[re-macos]]; with the __swift5_* section but symbol names stripped → it is still Swift (reflection metadata does not disappear with strip, see pitfall 5)
  2. mangling decoding (batch):

    llvm-nm -m sample | awk '$3 ~ /^\$s/ {print $3}' | swift-demangle | head -20
    # ELF: readelf -s sample | awk '$8 ~ /^\$s/ {print $8}' | swift-demangle
    

    Decoded content: module name/type/function signature/generic parameter expansion; results are written to the symbol table for renaming by [[re-ghidra]] / [[re-ida]]. For an overview of the mangling structure see step 3 and [[layout]].

  3. Mangling structure overview (reading symbols by hand):

    $s 前缀 + 长度前缀标识符 + 类型字母 + 签名
    $s 13ExampleModule 3Foo C 3bar Si y F
       └模块(13字符)   └类型 └类 └方法 └结果 └参数 └函数
    C=类 O=枚举 V=结构体 P=协议;Si=Int SS=String Sq=Optional;y=空元组 ()
    

    Encoding rules: identifier = decimal length + ASCII; signature order isresult type first, parameters after,F ending indicates a function; the signature may carry modifier bits (async/throws, appearing explicitly in the decoded output);_To suffix = @objc bridging thunk;TE suffix = distributed thunk. For detailed rules and official examples see [[layout]]/[[examples]].

  4. Protocol witness table (the core of dynamic dispatch):

    • witness table is the protocol method dispatch table; conformance record location: Mach-O __swift5_proto section / ELF swift5_protocol_conformances(protocol descriptors in __swift5_protos / swift5_protocols), or search _swift_getWitnessTable call site
    • Recovery: conformance record = method slot array, slot value = implementation address of the corresponding protocol requirement → combined with the vtable recovery approach in [[re-cpp-abi]], reverse-engineer "which type conforms to which protocol"
    • When there is no symbol name, infer from protocol method call sites: find the function pointer table at the witness method call site; the table order is the protocol declaration order
    • Note indirect conformance (witness_table_indirect flag): the slot stores an indirect table pointer rather than a direct implementation address, requiring one more level of resolution
  5. Closure capture:

    • A closure is a context object:swift_allocObject call site → context allocation → captured field initialization; the context header is a pointer to the capture layout descriptor, followed by fields arranged in capture order
    • Recovery: context struct fields → captured variable names/types (associated with the function signature); reference-semantics fields are confirmed by the swift_retain/swift_release call site
    • The compiler-generated "partial apply forwarder" symbol (whose decoding contains partial apply forwarder) is the typical form of a closure entry point
  6. ObjC interop:

    • @objc bridging: Swift classes are visible through the ObjC runtime (NSObject subclasses, objc_msgSend message sending); the Swift-side implementation entry point is the bridging thunk ending in _To trailing bridge thunk
    • Analysis: ObjC method list (class-dump approach) → bridging thunk → Swift implementation;swift_ distinguish Swift-side logic at runtime function call sites
    • Mixed artifacts: first follow the ObjC path ([[re-ios]] / [[re-macos]]) then enter the Swift layer; Swift artifacts on Linux have no ObjC layer (no __objc_classlist), so analyze from a pure ELF perspective
  7. Reflection metadata (type structure recovery):

    otool -l sample | grep -A2 swift5_fieldmd      # 字段描述(类型内布局)
    otool -l sample | grep -A2 swift5_typeref      # 类型引用(字符串池)
    otool -l sample | grep -A2 swift5_reflstr      # 反射字符串
    

    __swift5_fieldmd records type field names/type references; combined with __swift5_typeref it can recover struct/class layouts, faster than pure decompilation.

  8. Symbolication recovery: after batch-renaming decoding results, cluster and filter generic specialization symbols (with many Gy suffixes) by module/type, first recovering the "core methods of each type" before handling specialized copies (see Pitfall 2).

  9. Byte-level verification: when symbol extraction/section location output is abnormal, use xxd for byte-level verification (mangled names are length-prefix + ASCII, reflection sections are fixed-length records within a data section) — see [[examples]] for minimal byte samples and the verification workflow; don't discard a tool parse failure as "not a Swift artifact".

Cross-domain combination

  • [[re-binary-core]] gateway: this skill's home (selection tree "Swift Artifacts" branch)
  • [[re-ios]] / [[re-macos]]: ecosystem integration (ObjC layer, App layer analysis)
  • [[re-format-macho]]: Mach-O section/reflection section location toolchain
  • [[re-cpp-abi]]: vtable/witness table recovery approach shares the same origin
  • [[re-frida]]: runtime hook Swift functions (decoded symbol names can be used directly as hook points)
  • [[re-analyze/analysis-contract]]: symbol table passed according to the data contract

Common pitfalls and traps

  • mangling version differences: Symptom — newer-version Swift artifacts fail to decode or output garbled text; Cause — mangling scheme evolution ($s/$S/_T0 third generation); countermeasure—use swift-demangle from the same generation as or newer than the target compilation version,_T0 old symbols can be demangled by new tools, but new symbols cannot be demangled by old tools
  • generic expansion symbol explosion: symptom—decoding outputs a large number of $s...Gy generic instances; cause—generic specialization; countermeasure—cluster and filter by module/type, first restore the unspecialized prototype
  • witness table has no symbol names: symptom—dispatch table slots are all raw addresses; cause—protocol conformance has no named symbols; countermeasure—infer slot semantics by combining protocol method call sites (witness method call sites)
  • closure captures contain reference semantics: symptom—the context field is a reference-counted object; cause—captured variables contain class instances; countermeasure—confirm reference fields via swift_retain/release call sites, do not read them as value types
  • pure ObjC mistaken entry: symptom—no $s yet going through the Swift process; cause—the artifact is actually ObjC; countermeasure—confirm first in step 1 (no $s/__swift5_* means switch)
  • mangled names are not ordinary strings: symptom—strings/grep truncates or misses some symbols; cause—mangled names can contain \x01-\x1f control characters followed by pointer bytes (symbolic reference), not a clean string ending in \0 ; countermeasure—use the llvm-nm/readelf -s symbol table for symbol extraction, do not use strings as a fallback; the parser must refuse to read pointer bytes when encountering control characters
  • after strip the reflection section is still there but symbol names are gone: symptom—the__swift5_proto section is still there, the$s symbol list is empty; cause—strip only removes the symbol table, reflection metadata (conformance/fieldmd) belongs to the data section; countermeasure—rebuild type/protocol relationships from the reflection section, without relying on symbol names
  • _To suffix is treated as an ordinary symbol: symptom—foo.bar.bas(zim:) and @objc foo.bar.bas(zim:) two symbols coexist, the latter is missed; cause—_To is an @objc bridge thunk; countermeasure—_To and the body are analyzed in pairs, ObjC-side calls go through the thunk into the Swift implementation
  • entry is not from ObjC's main perspective: symptom—analyzing by the main function reveals the "entry" is only a thin layer; cause—the Swift executable entry is @main/top-level code goes through runtime initialization (swift_retain appears heavily on the startup path); countermeasure—from the main disassembly, follow the runtime call chain to find the first user type's method call site and then go deeper, do not circle around in the initialization wrapper
  • Swift 4 old artifacts have no reflection section: symptom—there is no __swift5_* section, and symbols have been stripped, leaving only _T/_T0 prefix remnants; cause—the reflection system was only introduced in 5.0; countermeasure—identify Swift-side code by mangling prefix (_TtC etc.) and swift_retain/swift_release runtime call sites, and supplement the type skeleton from the ObjC section (__objc_classlist)