When to use / when not to use
- Use: Swift artifacts (Mach-O/ELF containing Swift mangling features with the
$sprefix, 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, includesswift-demangle); Windows: official Swift toolchain - Verify:
echo '$s4main6myFuncyyF' | swift-demangle(on macOS usexcrun swift-demangle; with no arguments it reads stdin, outputs$s... ---> main.myFunc() -> ()) - Batch:
llvm-nm sample | swift-demangle;-simplifiedoutputs simplified names;-expandoutputs 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 sampleto see symbols and section attribution;otool -l sample | grep swift5to 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]]).
-
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)
-
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-demangleDecoded 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]].
-
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,
Fending indicates a function; the signature may carry modifier bits (async/throws, appearing explicitly in the decoded output);_Tosuffix = @objc bridging thunk;TEsuffix = distributed thunk. For detailed rules and official examples see [[layout]]/[[examples]]. -
Protocol witness table (the core of dynamic dispatch):
- witness table is the protocol method dispatch table; conformance record location: Mach-O
__swift5_protosection / ELFswift5_protocol_conformances(protocol descriptors in__swift5_protos/swift5_protocols), or search_swift_getWitnessTablecall 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_indirectflag): the slot stores an indirect table pointer rather than a direct implementation address, requiring one more level of resolution
- witness table is the protocol method dispatch table; conformance record location: Mach-O
-
Closure capture:
- A closure is a context object:
swift_allocObjectcall 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_releasecall site - The compiler-generated "partial apply forwarder" symbol (whose decoding contains
partial apply forwarder) is the typical form of a closure entry point
- A closure is a context object:
-
ObjC interop:
@objcbridging: 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_Totrailing 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
-
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_fieldmdrecords type field names/type references; combined with__swift5_typerefit can recover struct/class layouts, faster than pure decompilation. -
Symbolication recovery: after batch-renaming decoding results, cluster and filter generic specialization symbols (with many
Gysuffixes) by module/type, first recovering the "core methods of each type" before handling specialized copies (see Pitfall 2). -
Byte-level verification: when symbol extraction/section location output is abnormal, use
xxdfor 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/_T0third generation); countermeasure—use swift-demangle from the same generation as or newer than the target compilation version,_T0old 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...Gygeneric 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
$syet 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-\x1fcontrol characters followed by pointer bytes (symbolic reference), not a clean string ending in\0; countermeasure—use thellvm-nm/readelf -ssymbol table for symbol extraction, do not usestringsas 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_protosection is still there, the$ssymbol 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 _Tosuffix 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—_Tois an @objc bridge thunk; countermeasure—_Toand 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
mainfunction 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 themaindisassembly, 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/_T0prefix remnants; cause—the reflection system was only introduced in 5.0; countermeasure—identify Swift-side code by mangling prefix (_TtCetc.) andswift_retain/swift_releaseruntime call sites, and supplement the type skeleton from the ObjC section (__objc_classlist)