Indirect pointer change in Swift 6?

I've been working on a tool to print all of the protocol conformances defined in a Swift binary that is passed in as a argument.

I've been heavily relying on Echo and parts of SnapshotPreviews as references.

I recently picked this project back up and I'm having trouble resolving protocol descriptors with indirect pointers here, but it was (mostly?) working when I was last working on it about a year ago.

Has something changed in this area recently?

It seems to work pretty well against small test binaries, but when I run it against larger iOS binaries it prints some valid conformances along with lots of gibberish while also missing a lot of conformances.

1 Like

Assuming things haven't changed, these pointers are supposed to be absolute once loaded, i.e. in a live program, after resolving the indirect offset you'll end up with an absolute (normal) pointer. The thing is, that suggests the (indirect) value isn't necessarily a relative pointer when reading directly from a binary; it might be any sort of value that gets a fix-up from the dynamic loader. If it is in the current binary, relative to the base address would be a reasonable representation, but I don't think it's guaranteed. And if it isn't, it could be any sort of value that's going to get fixed up later with a relocation, and you wouldn't be able to read it out of the current binary anyway. That said, you can probably detect that this is the case because the value will be on a page with a bunch of other relocations, possibly even in its own section (I don't remember).

3 Likes

Thanks for the response!

I'm treating them as relative to the base address as you suggested would be a reasonable, but isn't actually guaranteed.

Do you have any suggestions for terms I could search for or places I could research to learn more about this topic?

The symbols command appears print everything I'm interested in finding, but that's either a closed source tool or I'm just bad at finding it online.

The Swift runtime has to resolve these pointers, but that's post-load, so it doesn't quite help. dyld and ld64 are open source, but that's probably a lot of work. I don't offhand have a suggestion of where to learn about Mach-O and relocations, but those are your search terms.

1 Like

I think the logic here is a little incorrect. After you've applied the relative offset from the this pointer, in the indirect case what the relative pointer points to is a direct pointer. E.g.

// indirect
if offset & 1 != 0 {
  let newPtr = this + (offset & ~1)
  let protocolPtr = newPtr.load(as: UnsafeRawPointer.self)
} else {
  // direct
}

To me it looks like you're loading a UInt32 from newPtr and applying that offset from the base address of the section...?

This is binary inspection, not live process inspection, so there are no direct pointers!

Ah I missed over that, thanks.

It turns out I just wasn't handling the Mach-O fat header correctly.

I pushed an update to my repository and it seems to be working correctly now.

Thanks for the pointers!

2 Likes

Not handling the fat binary header was definitely an issue in some situations, but after further testing I believe the original issue I ran into was that we've started using InjectionIII since last time I was working on this tool, which requires passing -Xlinker -interposable to debug builds.

I'm assuming this extra layer of indirection is what's causing my tool to fail on debug builds, but I'm having a hard time finding much information about what that flag does exactly. I've found some mentions of the global offset table, but must of the information I can find about that is about ELF files.

I don't have much experience in this area, so any advice on where to find resources or documentation would be much appreciated.

A couple of useful sources of information on interposing:

https://www.mikeash.com/pyblog/friday-qa-2012-11-09-dyld-dynamic-linking-on-os-x.html

Not sure why interposing would be interacting with the conformance tables though.

1 Like

You might look at the ObjectMemoryReader class implementation in the Swift sources for an example of extracting data offline from Swift metadata structures. That's what the swift-reflection-dump tool uses to dump reflection info from Swift binaries. It has to deal with a few different variations in how dynamically-linked references in Mach-O images are represented. One complication in particular you might see sooner or later is the encoding for signed pointers used for the "arm64e" slice of system libraries for Apple Silicon platforms.

6 Likes