8

This question and answer describe how to read data from a Mach-O section with Objective-C on modern OS X/macOS versions: Crash reading bytes from getsectbyname

The described answer works. I'm trying to implement the same thing with Swift. I can't make it work.

I have the following in "Other linker flags": -Wl,-sectcreate,__LOCALIZATIONS,__base,en.lproj/Localizable.strings,-segprot,__LOCALIZATIONS,r,r.

This Swift code gets me the a pointer to the embedded data, until I try to run the code outside Xcode and ASLR breaks it:

var size: UInt = 0
let _localizationSection = getsectdata(
    "__LOCALIZATIONS",
    "__base",
    &size)

To get around the ASLR problem, according to the above question and answer, and based on my own testing, I should be using getsectiondata instead. It works great in Objective-C, but I'm having no luck in Swift. The following is the only thing I've managed to get past the compiler, but it returns nil:

var size: UInt = 0
var header = _mh_execute_header
let localizationSection = getsectiondata(
    &header,
    "__LOCALIZATIONS",
    "__base",
    &size)

Is taking a copy of _mh_execute_header the problem and is there any way to avoid it? I need an UnsafePointer<mach_header_64>, but using &_mh_execute_header as the first parameter to getsectiondata causes a compilation error.

I'm using Swift 3.0, and running my code on macOS 10.12.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
Juri Pakaste
  • 1,402
  • 10
  • 15

1 Answers1

5

The difference between the linked-to Objective-C code

void *ptr = getsectiondata(&_mh_execute_header, ...);

and your Swift translation

var header = _mh_execute_header
let localizationSection = getsectiondata(&header, ...)

is that the latter passes the address of a copy of the global _mh_execute_header variable to the function, and apparently that is not accepted. If you modify the Objective-C code to

struct mach_header_64 header = _mh_execute_header;
void *ptr = getsectiondata(&header, ...);

then it fails as well (and actually crashed in my test).

Now the problem is that _mh_execute_header is exposed to Swift as a constant:

public let _mh_execute_header: mach_header_64

and one cannot take the address of a constant in Swift. One possible workaround is to define

#import <mach-o/ldsyms.h>
static const struct mach_header_64 *mhExecHeaderPtr = &_mh_execute_header;

in the bridging header file, and then use it as

let localizationSection = getsectiondata(mhExecHeaderPtr, ...)

in Swift.


Another option is to lookup the symbol via dlopen/dlsym

import MachO

if let handle = dlopen(nil, RTLD_LAZY) {
    defer { dlclose(handle) }

    if let ptr = dlsym(handle, MH_EXECUTE_SYM) {
        let mhExecHeaderPtr = ptr.assumingMemoryBound(to: mach_header_64.self)

        var size: UInt = 0
        let localizationSection = getsectiondata(
            mhExecHeaderPtr,
            "__LOCALIZATIONS",
            "__base",
            &size)

        // ...
    }
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • That reduces the amount of Objective-C I have to write and is a pretty decent solution if I already have a bridging header in the project, but is still a bit annoying in an otherwise pure Swift project. – Juri Pakaste Mar 23 '18 at 05:21
  • 2
    @JuriPakaste: I have added another (pure Swift) solution. – Martin R Mar 23 '18 at 08:00
  • @MartinR “one cannot take the address of a constant in Swift” … We can use `withUnsafePointer(to: _mh_execute_header)`, no? – fumoboy007 Apr 18 '19 at 08:35
  • @fumoboy007: Yes, that is new in Swift 4.x(?) – But I tried that and it does not work here for some reason. It compiles, but does not return the section data. – Martin R Apr 18 '19 at 08:42