8

This is essentially what I'm trying to do,

#include <sys/mman.h>

int zero() {
    return 0;
}

int main(int argc, const char *argv[]) {
    return mprotect((void *) &zero, 4096, PROT_READ | PROT_WRITE);
}

so I'm trying to make code writable, essentially. This doesn't work on the current macOS (Catalina 10.15.2), it just returns -1 and sets errno to EACCES, which as far as I know is because of lack of entitlement/code signing. I've found the entitlement I need to set, but I have no idea how to go about that, nor how to actually sign it..

If I run codesign -d --entitlements :- <path_to_app>, it fails with code object is not signed at all, even though I've tried configuring signing in Xcode for a while (I have a certificate and so on). So how should I go about this? Actually signing it isn't obvious with Xcode, so I'm fairly clueless.

  • It is the problem of `PROT_WRITE` in MacOS Catalina. We could not fix it by passing all the flags in entitlements. – tobe May 15 '20 at 11:17
  • We have test to codesign with all the entitlements, such as `com.apple.security.cs.allow-jit`, `com.apple.security.cs.allow-unsigned-executable-memory` and `com.apple.security.cs.disable-executable-page-protection` but it didn't work. This could be the issue of MacOS Catalina and hop developers from Apple could fix it. – tobe May 18 '20 at 03:50

1 Answers1

8

This is not a definitive answer, but it's a workaround.

Your problem is caused by changes of the linker (ld64) in macOS Catalina. The default value of the max_prot attribute of the __TEXT segment in the Mach-O header has been changed.

Previously max_prot default value was 0x7 (PROT_READ | PROT_WRITE | PROT_EXEC).
The default value has now been changed to 0x5 (PROT_READ | PROT_EXEC).

This means that mprotect cannot make any region that resides within __TEXT writable.

In theory, this should be resolved by providing the linker flag -segprot __TEXT rwx rx, but this is not the case. Since Catalina, the max_prot field is ignored. Instead, max_prot is set to the value of init_prot (see here).

To top it all off, init_prot cannot be set to rwx either due to macOS refusing to execute a file which has a writable __TEXT(init_prot) attribute.

A brute workaround is to manually modify and set __TEXT(max_prot) to 0x7 after linking.

printf '\x07' | dd of=<executable> bs=1 seek=160 count=1 conv=notrunc

Since that code snippet relies on the __TEXT(max_prot) offset being hardcoded to 0xA0, as an alternative, I've created a drop-in replacement/wrapper for ld which respects the max_prot parameter of segprot.

Elliott Darfink
  • 1,153
  • 14
  • 34
  • You can bypass the Catalina `dyld: malformed mach-o image: __TEXT segment maps start of file but is writable` check with the workaround I've shown here https://stackoverflow.com/a/60505259/5329717 – Kamil.S May 21 '20 at 09:35
  • That's an interesting tidbit! In the case of `mprotect`, I'd say it's better to alter the `max_prot` attribute instead, but it's great to know that there are alternatives. – Elliott Darfink May 21 '20 at 13:10
  • Thanks and it works for subhook programs with this command https://github.com/Zeex/subhook/issues/45 – tobe Jun 01 '20 at 02:33