Getting started with making macOS utility app using private APIs

Getting started with making macOS utility app using private APIs

While using macOS day to day life, you may feel something is missing, or probably you may find repeating yourself and need some help by the tools or utilities.

In this talk, based on the experiences to make a TouchBar utility app, HapticKey, we will discuss how to make a macOS utility app stays in the status bar. Also, we will see how to reverse engineering macOS undocumented private APIs to provide utility functionalities.

Expected audiences and knowledge:
- Those who want to improve their day to day life by themselves.
- Intermediate level of Apple platform knowledge.
- Beginner level of Objective-C knowledge.

The goal of this talk:
- Make `NSStatusBar` utility application.
- Reverse engineering undocumented private APIs and use it.

Following topics are non-goal of this talk:
- Basic knowledge about how to use Xcode
- Security problems related to reverse engineering.
- Swift

E4e01fb8b7105e61e3876224139503ab?s=128

Yoshimasa Niwa

September 08, 2019
Tweet

Transcript

  1. 1.

    Using Private APIs Getting Started with
 Making macOS Utility App

    Yoshimasa Niwa 9/8/2019 — TIME SHARING ࢛୩
 macOS native symposium #05
  2. 6.

    Today’s presentation Topics 1. We have no windows 2. We

    launch app when login 3. We watches Touch Bar events 4. We use track pad actuator
  3. 8.
  4. 9.
  5. 10.

    statusItemWithLength: NSStatusBarItem • applicationDidFinishLaunching: is still application entry point •

    Create NSStatusBarItem from NSStatusBar • Use NSStatusItemBehaviorRemovalAllowed to let users to arrange items in status bar.
 The position is persistent in NSUserDefaults
  6. 11.
  7. 12.

    LSUIElement is YES Hide dock icon • In Info.plist, set

    Application is agent (UIElement) to YES • That’s it
  8. 13.

    A few additional things Caveats • Provide a way to

    quit app, or user can’t quit app • Status bar menu is the main user interface to communicate with users. Provide simple, intuitive menu items • Status bar icon may represent application state, such as enabled, disabled
  9. 14.

    But we have status bar item and menu No main

    window • Use NSStatusBar and NSStatusBarItem • Set LSUIElement to YES • Status bar menu is the main user interface
  10. 18.

    You might find this API SMLoginItemSetEnabled • Enable a helper

    application located in the main application bundle’s “Contents/Library/LoginItems” directory
  11. 19.
  12. 20.

    Useless, unless you need to put your app in App

    Store SMLoginItemSetEnabled • It never work if you have two copy of application bundles that have same bundle identifier … and you will always have the two or more of them in the derived data and the archives • This is not adding Login Item to the account preferences. Completely different logic and more similar to /Library/LaunchAgents
  13. 21.

  14. 22.

    This is the one you need LSSharedFileList • Deprecated but

    still supported • You can add, remove Login Item • You can observe Login Item changes
  15. 23.

  16. 24.

    Example code seems working LSSharedFileList LSSharedFileListRef fileList = LSSharedFileListCreate(NULL,
 kLSSharedFileListSessionLoginItems,

    NULL); LSSharedFileListAddObserver(fileList, ..., callback, ...); void callback(LSSharedFileListRef fileList, void *context) { NSArray *snapshot = (__bridge NSArray *)
 LSSharedFileListCopySnapshot(fileList, ...); 
 for (item in snapshot) { CFURLRef url; LSSharedFileListItemResolve(item, ..., &url); if ([(__bridge NSURL *)url isEqualTo:applicationBundlerURL]) { // changed! } } }
  17. 25.
  18. 26.

    NSDistributedNotificationCenter stops working LSUIElement app is always inactive • LSSharedFileList

    is using NSDistributedNotificationCenter • LSUIElement app is always inactive • NSDistributedNotificationCenter will not deliver any notifications if app is inactive
  19. 27.

    NSNotificationSuspensionBehaviorDeliverImmediately Listen com.apple.private.BackgroundItemsChang • LSSharedFileList listens com.apple.private.BackgroundItemsChan geNotification • If

    the application process listens that notification as well with NSNotificationSuspensionBehaviorDeliv erImmediately option, LSSharedFileList can also listen it
  20. 29.

    • SMLoginItemSetEnabled is not what we want • LSSharedFileList is

    the one • LSUIElement app also need to listen com.apple.private.BackgroundItemsChan geNotification LSSharedFileList is the one but requires some tricks Launch app when login
  21. 31.
  22. 32.
  23. 34.

    Overview • macOS provides several APIs for event observing. ◦

    [NSView keyDown] ◦ [NSApplication sendEvent] ◦ CGEventTapCreate, [NSEvent addGlobalMonitorForEvents] ◦ IOHIDQueueRegisterValueAvailableCallback ◦ etc. • macOS Catalina (10.15) will require a user approval of input monitoring for security. (Congratulation!) https://docs.google.com/presentation/d/1nEaiPUduh1vjks0rDVRTcJaEULbSWWh1tVdG2HF_XSU/edit?usp=sharing
  24. 35.

    We can listen system event • CGEventTapCreate with event masks

    to listen specific events • We can listen keyboard events such as Fn keys • We can listen gesture events, which includes taps on Touch Bar CGEventTap
  25. 38.

    API is a set of functions that many application can

    use What is API? • macOS API is provided in shared libraries • In form of C or Objective-C or Swift • Newer API may not be implemented in Objective-C • Application are using shared libraries through dyld(1)
  26. 39.

    Public API is pubic because it is public Why public

    API is public? • Public API is public because it is documented • Public API is public because its name is visible • Public API is public because it is allowed to access from the application
  27. 40.

    Private API is private because it’s not public Why private

    API is private? • Private API is private because it is not documented • Private API is private because its name is not visible • Private API is private because it is not allowed to access from the application
  28. 41.

    Private API is private because it’s not public Why private

    API is private? • Private API is private because it is not documented • Private API is private because its name is not visible • Private API is private because it is not allowed to access from the application
  29. 42.

    A container contains all related resources Framework Bundle • API

    is in shared library, which is lay outed in .framework bundle format • Public one is in /System/Library/Frameworks • Private our is in /System/Library/ PrivateFrameworks
  30. 43.

    /System/Library/PrivateFrameworks/MultitouchSupport.framework Use MultitouchSupport • A private framework that provides multitouch

    track pad related functions on macOS • This framework also provides track pad vibration
  31. 45.

    Command line tools Hands-on tools • Use nm -m dylib

    to find exported function names • Use otool -t -V -p function dylib to disassemble function • Use clang -iframework /System/ Library/PrivateFrameworks -framework framework to link private framework
  32. 46.

    Example code @import Foundation; @import IOKit; CF_EXPORT CFTypeRef MTActuatorCreateFromDeviceID(UInt64 deviceID);

    CF_EXPORT IOReturn MTActuatorOpen(CFTypeRef actuatorRef); CF_EXPORT IOReturn MTActuatorClose(CFTypeRef actuatorRef); CF_EXPORT IOReturn MTActuatorActuate(CFTypeRef actuatorRef, SInt32 actuationID, UInt32 unknown1, Float32 unknown2, Float32 unknown3); int main() { IOReturn error; CFTypeRef actuatorRef = MTActuatorCreateFromDeviceID(0x200000001000000 );
 if (!actuatorRef) {
 return -1;
 } error = MTActuatorOpen(actuatorRef);
 if (error != kIOReturnSuccess) {
 CFRelease(actuatorRef);
 return -1;
 } error = MTActuatorActuate(actuatorRef, 6, 0, 0, 0.2); error = MTActuatorClose(actuatorRef); CFRelease(actuatorRef);
 actuatorRef = NULL; return 0; } Hands-on sample
  33. 48.

    Disassembling, researching and guessing. Private API • nm, otool, and

    also lldb • Hopper Disassembler is the tool helps your research • Always guess how API is designed and works