executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
re-format-machoMach-O 格式解析:mach_header、LC_*、segment、dyld 信息。 触发词:Mach-O、解析mac程序、dyld
Independent GitHub snapshot · Automated classification · Original documentation governs
参考 [[re-analyze/platform-tips]] macOS 分支——attach/调试受 SIP 与 TCC 限制;静态解析不受影响。
xcode-select --installotool --versionllvm-otool(apt install llvm / brew install llvm / pacman -S llvm),功能覆盖 otool 常用子命令brew install llvm;Linux: apt install llvm / dnf install llvm / pacman -S llvmllvm-objdump --versionllvm-objdump --macho --private-headers sample(Linux 上可直接解析 Mach-O,与 macOS 输出一致)make,或下载 release 二进制jtool2 -h sample 输出 load commandscodesign --version;lipo -info samplellvm-lipo -info sample(llvm 自带)/usr/bin/dyld_shared_cache_util);用途: 解析 dyld 共享缓存中的系统 dylib 时把单个库抽出来初勘(先确认类型与架构):
file sample # 可执行文件 / dylib / 通用二进制 / 字节序
lipo -info sample # 通用二进制列出各架构片;单架构提示 "non-fat file"
xxd -l 16 sample # 魔数: cffaedfe=64位小端, feedfacf=64位大端, cebafaed=32位小端, cafebabe=fat
魔数(文件里看到的字节):cffa edfe(MH_MAGIC_64)、cefa edfe(MH_MAGIC 32 位)、cafe babe(FAT,后跟 4 字节架构数)。通用二进制先 lipo -thin <arch> -output piece sample 提取单片再解析。
mach_header 与 load commands(核心):
otool -h sample # magic/cputype/filetype/ncmds/flag 一行全出
otool -l sample | head -80 # 全部 load commands
64 位 mach_header 固定 32 字节(32 位为 28 字节):magic(4) + cputype(4) + cpusubtype(4) + filetype(4) + ncmds(4) + sizeofcmds(4) + flags(4) + reserved(4,仅 64 位)。load commands 是 Mach-O 的"节表+导入表+入口+签名"综合体——从头到尾按 cmdsize 顺序遍历,ncmds 计数、sizeofcmds 为总字节数,两者互相校验。filetype: 2=MH_EXECUTE 6=MH_DYLIB 8=MH_BUNDLE。常见 LC 值与字段表见 [[layout]]。
段与节(__TEXT/__DATA/__LINKEDIT):
otool -l sample | grep -A8 'segname __TEXT'
otool -l sample | grep -A8 'segname __DATA'
llvm-objdump --macho --section-headers sample
记录各段 vmaddr/vmsize/fileoff;__TEXT,__text 是代码,__DATA,__data 是可写数据,GOT 在 __DATA_CONST,__got(只读常量段;旧二进制在 __DATA,__got);__LINKEDIT 是有 vmaddr/vmsize 的正常段,通常映射为只读区域(vmmap 里能看到),存放符号表/字符串表/重定位/签名等链接元数据——具体存在方式受 dyld 与 shared cache 影响。
入口(LC_MAIN / LC_UNIXTHREAD):
otool -l sample | grep -A6 LC_MAIN
entryoff 是入口相对文件头的偏移,入口 VA = __TEXT 段 vmaddr + entryoff(64 位下 __TEXT 基址通常 0x100000000)。老二进制用 LC_UNIXTHREAD(寄存器 state 含入口)。可执行文件无 LC_MAIN 且无 LC_UNIXTHREAD 属畸形,重点排查。
dyld 信息(LC_DYLD_INFO_ONLY / 导出 trie):
otool -l sample | grep -A9 LC_DYLD_INFO_ONLY
llvm-objdump --macho --exports-trie sample # 导出符号(trie 解码)
offset+size 共五组、合计 48 字节,都在 LC_DYLD_INFO_ONLY 里:rebase(基址修正点)、bind(外部符号绑定,非惰性)、weak_bind(弱符号绑定,弱符号合并语义独立,不得并入 bind)、lazy_bind(惰性绑定)、export(导出 trie);新版二进制用 LC_DYLD_CHAINED_FIXUPS(链式修正,iOS 13+ 默认)。dylib 的导出符号在 trie 里,nm -gU 同效果。
动态库依赖链(注入检测):
otool -L sample # LC_LOAD_DYLIB 列表(依赖库 + install name)
otool -l sample | grep -A4 LC_LOAD_DYLIB
每个 LC_LOAD_DYLIB:name(绝对路径或 @rpath/@executable_path 相对)+ timestamp/current_version。恶意 dylib 持久化 = 新增 LC_LOAD_DYLIB 条目——与签名时列表比对不一致是重要告警(详见坑)。two-level namespace 下符号查找按 (库, 符号) 二元组,导入解析严格。
符号表(nlist_64):
llvm-nm -m sample # 含段归属与 N_EXT 标记;undefined = 导入
llvm-nm -gU sample # 仅外部定义符号(导出)
otool -l sample | grep -A3 LC_SYMTAB # symoff/nsyms/stroff/strsize 定位表
nlist_64 每条 16 字节:n_strx(字符串表偏移)+ n_type(N_EXT/N_SECT 等)+ n_sect + n_desc + n_value。stripped 后 .symtab 消失,只剩 LC_DYLD_INFO 的导出 trie 与动态绑定信息——恢复思路同 ELF([[re-imports]] 签名匹配 + 字符串交叉引用)。
代码签名检查:
codesign -dv sample 2>&1 # 签名类型(adhoc / Apple Development / not signed)
codesign --verify --deep sample # 完整性验证
jtool2 --sig sample | head -20 # LC_CODE_SIGNATURE 细节(CodeDirectory/哈希)
adhoc:自签,可被修改后重签(codesign -f -s - sample)手工解析与验证:load command 输出异常/头字段被伪造时,用 xxd + Python struct 按偏移直接解析 mach_header 与各 LC(最小可运行示例与字节样例见 [[examples]]),别把解析失败当"损坏文件"丢弃。
codesign -f -s - 重签(仅限自签/adhoc 场景);Library Validation 开启时 DYLD_INSERT_LIBRARIES 失效otool -L 列表与签名时列表不一致是重要告警lipo -info 确认架构再 lipo -thin 提取,别对全片解析;fat 头里各片 offset 是绝对文件偏移,按 (offset, size) 切出单片__DATA_CONST,__got(只读,装载后重定位一次);旧产物在 __DATA,__got(可写)——找 GOT 先 otool -l 看段名,别假设/System/Library/dyld/ 缓存内,文件系统里只有 stub——分析系统库用 dyld_shared_cache_util -extract 抽出otool -l 输出中途乱码/报错;原因——ncmds 与 sizeofcmds 被伪造或某条 LC 的 cmdsize 异常;对策——所有 LC 至少 8 字节(load_command 头本身 cmd+cmdsize),64 位下 cmdsize 须为 8 的倍数、32 位为 4 的倍数,再按具体 cmd 校验其结构最小尺寸与尾随 section/字符串,累计不超 sizeofcmdsvmmap 可见);不过 shared cache 共享库的链接元数据驻留在共享缓存里,直接从进程内存按文件偏移找符号表常常对不上——定位符号表优先用 nm/dyldinfo/MachOView 等按 LC_SYMTAB 解析,而不是照抄文件偏移