Handmade macOS executable
Asked Answered
N

1

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)

Namangan answered 29/8, 2021 at 23:11 Comment(13)
My answer here #39863612 clearly predating m1 chip still might come handy. – Nottingham
You can also try padding the executable size to 16kb for arm64/m1. – Nottingham
Even with 16kb padding before the __text section I still get SIGKILL – Namangan
I meant the whole executable size to be 16 kb. Can you please provide the actual WIP executable file? – Nottingham
Sure thing, there you go. PS: Dumb me, didn't understand you were talking about the whole file size ;-( – Namangan
Not sure if that's the cause but move __LINKEDIT virtual memory address to a multiple of 0x1000 in your case 0x00000001000040FC to 0x0000000100005000 – Nottingham
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? – Namangan
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. – Nottingham
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 – Namangan
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: #60498396 – Nottingham
That sounds like a good option. I'll give it a shot sooner or later – Namangan
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. – Dyeline
I did, it doesn't work anyway – Namangan
B
8

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.

Bathypelagic answered 30/8, 2021 at 11:8 Comment(2)
I was eventually able to make the code signing happen by adding an empty __LINKEDIT segment, but I still get SIGKILL πŸ₯². – Namangan
That error gives you something to work with, at least. See this file 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. – Bathypelagic

© 2022 - 2024 β€” McMap. All rights reserved.