6

I'm trying to implement a tiny compiler for macOS. I'm running macOS 11.5 on a MacBook Pro with M1. The assembly encoding works fine and I'm quite happy with the result (when handed over to Clang compiles and runs just fine).

My problem is that I couldn't find a way generate a valid executable file on my own. I got to a point where radare2 disassembles correctly every part of the executable, but every time I try to run my executable I get SIGKILL (9) from the terminal.

I read this whole file since I couldn't find any other source of documentation on the Mach-O format. SPOILER: It didn't work very well , that is why I'm hoping on some kind of Mach-O wizard to read this.

My problem in detail: The Mach-O header is fine. My problem is all about load commands.

I tried to inject the following segments/commands:

  • __PAGEZERO
  • __TEXT
  • __TEXT,__text
  • __LINKEDIT
  • LC_DYLD_INFO
  • LC_LOAD_DYLINKER
  • LC_MAIN
  • LC_LOAD_DYLIB

but no matter what I tried (I even tried to copy their values from other executables and then I "replaced" the address of the entry point to match mine), I couldn't find a way to make my executable file work.

Does anybody know what are the exact load commands I need to inject into the executable and their values?

PS: I would be happier if there was a way not to use dyld (I'm planning to stick with syscalls)

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • My answer here https://stackoverflow.com/questions/39863112/what-is-required-for-a-mach-o-executable-to-load/42399119#42399119 clearly predating m1 chip still might come handy. – Kamil.S Aug 31 '21 at 13:01
  • You can also try padding the executable size to 16kb for arm64/m1. – Kamil.S Aug 31 '21 at 13:15
  • Even with 16kb padding before the __text section I still get SIGKILL –  Aug 31 '21 at 13:26
  • I meant the whole executable size to be 16 kb. Can you please provide the actual WIP executable file? – Kamil.S Aug 31 '21 at 13:54
  • Sure thing, [there you go](https://drive.google.com/file/d/11WHPRPFvXexSZi9IT49Nb63b4IQZw1gE/view?usp=sharing). PS: Dumb me, didn't understand you were talking about the whole file size ;-( –  Aug 31 '21 at 14:01
  • Not sure if that's the cause but move `__LINKEDIT` virtual memory address to a multiple of `0x1000` in your case `0x00000001000040FC` to `0x0000000100005000` – Kamil.S Aug 31 '21 at 15:07
  • I moved the `__LINKEDIT` segment to 0x4000 and shifted back the __TEXT,__text section. I even increased the padding to 0x3FFC to get at least 16KB large binaries, but I still get that dammed SIGKILL. Do you have any other suggestion sir? –  Aug 31 '21 at 17:45
  • In the MH_header you can try cpu subtype `0x80000002` i.e. `CPU_SUBTYPE_LIB64|CPU_SUBTYPE_ARM64E`. in place of your `0x2` `CPU_SUBTYPE_ARM64E`. Also you could try dropping the `MH_PIE` flag for extra diagnostics. In MacOS Console app you could inspect the system.log of the System kind but I'm not sure if you'll learn anything other than it's `SIGKILL`. – Kamil.S Aug 31 '21 at 19:50
  • Indeed I didn't find anything useful. I give up and make my language interpreted. Thanks everybody for the help you gave me, but it turns out that it simply won't work... _dammed macOS strict checks_ –  Aug 31 '21 at 20:04
  • I'll happily revisit the topic once I get an m1 or its successor. In the meantime I'd go the path of getting a minimal working example yielded from genuine linker and strip away whatever is possible and inspect if it still survives kernel checks. You can remove load commands from an executable like this: https://stackoverflow.com/questions/60497896/self-modifying-code-on-darwin-10-15-resulting-in-malformed-mach-o-image/60505259#60505259 – Kamil.S Aug 31 '21 at 20:12
  • That sounds like a good option. I'll give it a shot sooner or later –  Aug 31 '21 at 20:26
  • Did you try with plain `arm64` instead of `arm64e` as architecture? AFAIK `arm64e` is only allowed for some system components still; even if you build a regular C hello world with `-arch arm64e`, it gets killed when you try to start it. – mstorsjo Sep 01 '21 at 10:02
  • I did, it doesn't work anyway –  Sep 03 '21 at 11:23

1 Answers1

7

You should be able to ditch dyld if you use LC_UNIXTHREAD instead of LC_MAIN.

But at least one reason you get killed is because you don't have a code signature. While x86_64 code is allowed to run without one, arm64(e) absolutely must have one. Apple even modified their linker to implicitly add an ad-hoc code signature for arm64 (which you can disable with -Wl,-no_adhoc_codesign, but then it's not gonna run).
Try running codesign -s - path/to/binary on your crafted file and see if that makes it work. I can't be sure that's the only problem with your binary, but it is certainly one. And if you would like to generate these code signatures yourself too, then the most straightforward code to look at will likely be libcodedirectory.c in ld64 source. Apple has also open sourced much more sophisticated codesigning code, and there's 3rd-party implementations like ldid.

Siguza
  • 21,155
  • 6
  • 52
  • 89
  • 1
    I was eventually able to make the code signing happen by adding an empty __LINKEDIT segment, but I still get SIGKILL . –  Aug 30 '21 at 23:10
  • 2
    That error gives you something to work with, at least. See [this file](https://opensource.apple.com/source/Security/Security-59754.140.13/OSX/libsecurity_utilities/lib/macho++.cpp.auto.html) for instances of `mSuspicious`. This isn't gonna be exactly the same as the checks in the kernel, but it should be a good starting point. – Siguza Aug 30 '21 at 23:55