Like with any simulator, it was immensely gratifying watching third-party code run in an entirely controlled virtual environment!
This project also got me very familiar with some unintuitive AArch64 addressing rules, and taught me the internals of how funny Objective-C constructors that accept variadic arguments lists are handled.
In one late-night push, I managed to get the real CoreFoundation library dynamically loaded and running, which meant that the real standard library was able to create bona-fide Objective-C objects!
This post also goes into some fun bugs I created for myself, such as the simulator skipping over undefined functions resulting in an infinite loop when simulated code tried to abort(). I hope it's fun to follow along with, and thanks for reading!
I have one question: Did you really implement this in Python? Why not C++ or Rust?
Yes, this was all in Python. This simulator was a bit unique in that there was no live user interaction, and therefore no realtime required. Overall, performance wasn't too much of a concern.
More particularly, I built this on top of strongarm, a Mach-O analysis library I had already written in Python. My employer encouraged us to write things in Python so that there was lower cognitive overhead for people to see each other's projects. This was more of an off-time passion project, but I still took the advice.
The goal here was to run small bits of the app's business logic. Specifically, it was about inspecting (for example) the contents of an NSDictionary that was passed to a particular system API.
My initial goal was to improve my own iOS static analysis tooling, and the way I initially thought about doing this was building a decompiler and then introspecting on the decompiled code. I didn’t have any knowledge of decompilers, and so the approach I took was essentially simulating the code and logging what it did. About a month down the line I realised that I was actually building a simulator in earnest, and ran with it.