Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

blueshogun96

macrumors regular
Original poster
Nov 24, 2012
112
3
After reading several posts on StackOverflow, I've gathered enough information on how to actually implement API hooking for macOS using C++ primarily. However, there's several questions that I have not managed to find a full/complete answer to after maybe months of searching on and off. Hopefully someone has some level of experience in this.

What I'm doing is writing a general purpose GPU performance tool similar to Fraps and GPU-z for macOS. In order for this to work, API hooking APIs in existing programs is absolutely necessary. So, the APIs being hooked are primarily gfx ones like OpenGL and Metal. This is actually quite simple to do on Windows but for macOS, it appears to be trickier, especially if the app being hooked is already running.

So, I've found a handful of ways to hook libraries, but so far the only one that offers the functionality I need is mach_inject. From what I'm told, this works but since El Capitan due to restrictions on calling task_for_pid(), you need to have SIP disabled, have root access or be added to procmon I believe it's called? So, that doesn't sound like something that would be "user friendly", at least not out of the box. Is there some sort of entitlement I can add to make things easier or what? According to this link here, I can add this entitlement and run w/ a sudo command (I actually just found this as I was gathering the information for this thread). Since it's only the background processes that need to run w/ a sudo command I'm okay with this but can you launch a program from your main app with such privileges programmatically so that task_for_pid() will run?

Also, keep in mind that I do not have a Mac that supports anything beyond El Capitan atm, so I'll likely get a Mac Mini with a Mojave/Catalina dualboot. The reason for the dual boot is that I want this to work for both 32-bit and 64-bit apps which will require two separate processes anyway, only on Catalina the 32-bit side just will not be run since it's not supported anyway.

It's really late here so I'm sorry if I sloppily wrote this and I should attempt this again in the morning and continue my research on this. Any ideas? Thanks.

Shogun
 
Excellently written post. And a very interesting project.

I've never needed to run a GUI app with sudo, but I know it's definitely possible to do privilege escalation like that, where the user is presented with a password box when the sudo process is spawned. For instance, with the tool Turbo Boost Switcher, when you disable Turbo Boost, it loads its kernel module with sudo privileges, and the sudo box is spawned on click. Running the whole program as sudo never shows the box. Are you forking a child process or just using a separate thread?
I know there's an API called something like AuthorizationExecWithPrivileges that may be relevant, but Stack likely has more details with a search like "Root privileges for running GUI process" or similar

As for the OS situation; You can patch an older unsupported Mac to work with a later release.
I'd also happily test for you if you need - I currently have the exact described setup. My Mac is Mojave with an external SSD holding Catalina (and Ubuntu Mate). It's very handy for the flexibility of having both 32-bit support and Catalyst programming abilities
 
Hi, sorry for the slow response (this quarantine is a blessing in disguise time wise but so much to do while the time at home remains), I've been trying to get a better understanding of how mach_inject actually works and how one would actually use it to inject code. I'm actually quite confused (that or I'm not reading it right), so that's been rather frustrating.

As for the privilege issue, I took your advice and looked up that function called "AuthourizationExecuteWithPrivileges" and took a minute to read the API documentation. It's deprecated as of now, but I guess it will suffice. As long as I can get a password prompt before execution that would be the most user friendly way because it looks like even the GUI application is going to require such privileges as task_for_pid() will be necessary for inter-process memory communications.

As for the child process, I am forking a child process as the two background processes (daemons) need to run separately from the GUI application and enumerate processes for both 32 and 64 bit separately. Normally, the number of 32-bit processes running is rather low. So far, the only 32-bit game that I'm running on my machine that is native to macOS is Bejeweled 2. Others would likely be just a few games running via WINE or something.

This unsupported Mac is running El Capitan, but can I really get Catalina or Mojave running on this old thing? And sure, I'd gladly accept your generous offer. I find that having a wide variety of hardware/OS setups is best when doing proper testing of software (I've done way too much professional software testing, even though I hate testing).

Thanks again for your input,

Shogun
 
Hi, sorry for the slow response (this quarantine is a blessing in disguise time wise but so much to do while the time at home remains), I've been trying to get a better understanding of how mach_inject actually works and how one would actually use it to inject code. I'm actually quite confused (that or I'm not reading it right), so that's been rather frustrating.

Don't you worry about it. I've been busy with a lot of tasks myself; Both a lot of university work, setting up a website on a Raspberry Pi, coding a little joke game with ncurses, and playing load of guitar, haha :p
I've never looked into mach_inject. I can see however that the GitHub talks about familiarity with assembly and ABI conventions, and if you have issues with that specifically I can help, otherwise I don't really have anything other than sympathy for the frustration to offer.

As for the privilege issue, I took your advice and looked up that function called "AuthourizationExecuteWithPrivileges" and took a minute to read the API documentation. It's deprecated as of now, but I guess it will suffice. As long as I can get a password prompt before execution that would be the most user friendly way because it looks like even the GUI application is going to require such privileges as task_for_pid() will be necessary for inter-process memory communications.

Ah. Didn't know it was deprecated. I'm sure they've replaced it with something though, whatever that may be. - Seems the Service Management framework would be the closest thing. - Also, I don't know if it could even facilitate your needs, but you may want to look at Apple's XPC library for interprocess communication. - Literally all I know about it is that it's Apple's system library for interprocess communication though - I don't know which functions specifically it exposes. Just thought it might be a helpful pointer.

This unsupported Mac is running El Capitan, but can I really get Catalina or Mojave running on this old thing? And sure, I'd gladly accept your generous offer. I find that having a wide variety of hardware/OS setups is best when doing proper testing of software (I've done way too much professional software testing, even though I hate testing).
DosDude1's Catalina/Mojave patcher supports a wide variety of Macs not supported by Apple - check your machine on the list to see.
 
Don't you worry about it. I've been busy with a lot of tasks myself; Both a lot of university work, setting up a website on a Raspberry Pi, coding a little joke game with ncurses, and playing load of guitar, haha :p
I've never looked into mach_inject. I can see however that the GitHub talks about familiarity with assembly and ABI conventions, and if you have issues with that specifically I can help, otherwise I don't really have anything other than sympathy for the frustration to offer.
Fortunately, I do understand how x86 (and x64) assembly works. Although I find it annoying that XCode doesn't show you the entire address space when debugging in assembly mode like Visual Studio does, this has been a pain to debug. I did figure out how to use mach_inject (the C API, not the Obj-C one) but it keeps crashing the target process after injecting the thread! The problem is that I can't seem to debug the code directly, so maybe I'll have to use Valgrind? If so, I'll have to learn how to use it. Are there any debuggers that work similar to WinDBG? At least the example code on the osxinj github page works so where there's a will there's a way.

I started another thread about it here: https://forums.macrumors.com/threads/api-hooking-mach_inject-keeps-crashing-within-thread.2233686/

I figured it would be easier to keep that in a separate thread as there's other things I'm working out in terms of API hooking since mach_inject is not the only way to do it.

Ah. Didn't know it was deprecated. I'm sure they've replaced it with something though, whatever that may be. - Seems the Service Management framework would be the closest thing. - Also, I don't know if it could even facilitate your needs, but you may want to look at Apple's XPC library for interprocess communication. - Literally all I know about it is that it's Apple's system library for interprocess communication though - I don't know which functions specifically it exposes. Just thought it might be a helpful pointer.
The XPC library is new to me. So I looked it up and it's Objective-C based so for me it's auto-cringe. So what I think I'll just do is use xnumem and allocate some virtual memory that the parent and child processes can access while using semaphores to control access to it.

DosDude1's Catalina/Mojave patcher supports a wide variety of Macs not supported by Apple - check your machine on the list to see.
That reminds me, I saw an old 2007-ish Macbook that had Catalina installed on it. It was slow as crap but at least it worked. Considering that there's no NV driver for Catalina last I checked, I imagine that would make things a challenge for me because this is a GPU monitoring tool and is dependent on the driver's abilities to report certain details.

Shogun
 
Fortunately, I do understand how x86 (and x64) assembly works. Although I find it annoying that XCode doesn't show you the entire address space when debugging in assembly mode like Visual Studio does, this has been a pain to debug. I did figure out how to use mach_inject (the C API, not the Obj-C one) but it keeps crashing the target process after injecting the thread! The problem is that I can't seem to debug the code directly, so maybe I'll have to use Valgrind? If so, I'll have to learn how to use it. Are there any debuggers that work similar to WinDBG? At least the example code on the osxinj github page works so where there's a will there's a way.

Good :). - I've never used WinDBG and don't know how it works. Xcode uses LLDB as it's debugger which seems pretty good but I'm not super familiar with its syntax since I've mostly used GDB for low level stuff. I like GDB though. The command layout regs with GDB gives a lovely overview of all the registers in the CPU. I don't know if it's the same between Visual Studio and Visual Studio Code, but if the debugging experience is similar you could just use VSCode.
As for valgrind it's good for showing if you have memory leaks but I don't know much about any other functionality it might have.

I figured it would be easier to keep that in a separate thread as there's other things I'm working out in terms of API hooking since mach_inject is not the only way to do it.

Yeah - Just left a quick reply above; Don't think I have the specific knowledge to give a more detailed response to your other thread

The XPC library is new to me. So I looked it up and it's Objective-C based so for me it's auto-cringe. So what I think I'll just do is use xnumem and allocate some virtual memory that the parent and child processes can access while using semaphores to control access to it.

Hehe, yeah. I've never really tried learning Objective-C, but whenever I see Objective-C code it always looks rather ugly with the syntax. The square brackets and use of spaces to access members and such, it's not really elegant I find. C++ was a better C with classes than obj-C.

That reminds me, I saw an old 2007-ish Macbook that had Catalina installed on it. It was slow as crap but at least it worked. Considering that there's no NV driver for Catalina last I checked, I imagine that would make things a challenge for me because this is a GPU monitoring tool and is dependent on the driver's abilities to report certain details.

Yeah if you have an NV GPU it may be an issue. But then again there are officially supported Macs with Nvidia GPUs, like the 750M. So whilst Nvidia doesn't offer their own drivers for Catalina you can still have Metal support for Nvidia with an Apple driver, though I don't know how that works with unsupported Macs and DosDude's work; If he has patched in Nvidia drivers for other unsupported GPUs - In any case it wouldn't be the latest drivers.
 
"Hehe, yeah. I've never really tried learning Objective-C, but whenever I see Objective-C code it always looks rather ugly with the syntax. The square brackets and use of spaces to access members and such, it's not really elegant I find. C++ was a better C with classes than obj-C."

It's not any uglier than C++. It's just a matter of what you're used to. In some ways programming with Objective C and Cocoa is better than C++. C++ does some things that are ugly, such as changing operator precedence, requiring lots of parentheses or rewriting C source code. Also there's no nightmare of multiple inheritance with Cocoa/Objective C.

Back to the original question: Apple has made mac OS applications much more secure with technologies like sandboxing, code signing and code hardening. Won't these make hooking much more difficult? Particularly code hardening.

Apple Developer's article about hardened runtime: https://developer.apple.com/documentation/security/hardened_runtime
 
Last edited:
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.