There are several aspects for this:
- if the app is not protected with some sort of packaging
- you just need to find the place that you want to patch with the help of debugger (when the app is running) or static disassembler (otool, hopper disassembler, ida disassembler)
- understand how many bytes your patch takes (for example with online tool)
- understand how many space you have in the original app. Every disassembler will show you or use the above tool as well
- use any hex editor (hex fiend) to patch the file with the bytes you've got from (1).
- if there is enough space for the patch, you apply it and the left space fill with
- if there is not, you will need to find that space by:
- splitting your patch in several parts (there are always caves between functions or at the end of the section)
- or allocate your own section and fill in your code. In that case, you will also need to edit the file adder to account for a new section.
- if the app is packed or protected in any way from patching, then you can not just patch it on disk just like that. You'll need to unpack it, patch as explained above and repack (or not) back.
- or you can create runtime patcher in this case, which is more complicated to do.
To automate the on-disk patching, one can use a pretty easy way which is based on some pattern matching and a little bit of heuristics:
- with the help of disassembler try to find some distinguishable bytes before the patch area
- do the same for the area after the patch area
- if appropriate, find the same for the patch itself:
- for example if there is a control flow change, try to analyze the target of that change for some pattern.
All the above will be sufficient (hopefully) to accommodate version changes and assure that you change at the right place. Such patcher could be easily coded with Python. For disassembling you can use Capstone library which has Python binding.
Take into account that, you fill make OSX complain about this patched app as it will not be signed any more.