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.
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).
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.
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...?
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.
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.