macOS 10.15 deployment breaks monkeypatching C++ vtable

How do we get this code to not crash? This was working up until we bumped our macOS deployment to 10.15. When deployment is set to macOS 10.14, the code works fine. Data is nearly identical in the debugger, although the vtable is at a slightly lower address in 10.15.

Have the C++ vtables been put in read-only marked pages, and if so how do we prevent that? Hardened runtime is not enabled, and I don't recall any mention from Apple about this change.

Code Block
#import <Foundation/Foundation.h>
class Base
{
public:
virtual ~Base() {};
virtual const char* Print() { return "Base"; }
};
class Derived : public Base
{
public:
virtual const char* Print() override { return "Derived"; }
};
const char* PrintPatch( Base* localThis )
{
return "Patch";
}
template <class T1, class T2>
void* PatchVtablePtr( void** vtable, T1 memberFunction, T2 newFunction )
{
// Replace the instance of memberFunction in the vtable with newFunction
void* offset = *(void**)&memberFunction;
auto vtableIndex = (uintptr_t)offset / sizeof(void*);
vtable[vtableIndex] = (void*)newFunction; <- Thread 1: EXC_BAD_ACCESS (code=2, address=0x100004038)
// return the original vtable address
return offset;
}
int main(int argc, const char * argv[]) {
Derived* derived = new Derived();
printf("%s\n", derived->Print());
// monkeypatch the vtable
//Base* base = derived;
//void** vtable = *(void*)base;
void vtable = *(void***)derived;
PatchVtablePtr( vtable, &Derived::Print, &PrintPatch );
printf("%s\n", derived->Print());
return 0;
}

Have the C++ vtables been put in read-only marked pages … ?

Yes. A quick trip to vmmap reveals that the v-table is in the __DATA_CONST section. I was vaguely aware of this change — it seems like a clear security win — but I don’t track C++ closely and so I’m not sure of all the details.

if so how do we prevent that

I think you’d be better off tweaking your patch code to un-write protect the page before you patch (using mprotect) and then re-write protect the page after. That preserve the bulk of the security benefits of this change. I prototyped this here and it seems to work (on macOS 11.2.3).

Are you using this patching technique only during development? Or does this patching code run on customer systems?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
I believe this code is only used during development, but I only found out it broke trying to move our base deployment off of 10.9. 10.14 worked, and 10.15 didn't.

On another thread, I'd also mentioned that iOS 10+ blocking mmap also prevents C++ dylib hotloading. I feel like blocking this prevents all gamedevs from hotloading their C++ game logic. That's a big deal when you're trying to modify code and content quickly. As games get bigger in size and complexity, the time to launch increases. Even Unity or Unreal require a reload after engine changes. If Apple's going to really push game tech, then this would be a big help even if we have to set a security/dev-only entitlement.

Anyways, Quinn thanks so much for the quick response on this.
macOS 10.15 deployment breaks monkeypatching C++ vtable
 
 
Q