Skip to content

Instantly share code, notes, and snippets.

@pudquick
Last active November 20, 2020 01:53
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pudquick/0c00aa1488c5184f8c1976bc265b7f7d to your computer and use it in GitHub Desktop.
Save pudquick/0c00aa1488c5184f8c1976bc265b7f7d to your computer and use it in GitHub Desktop.
Discovering variable/constant values in frameworks
import objc
import types
from ctypes import cast, c_void_p, POINTER
from Foundation import CFBundleCreate, NSURL, NSBundle
pointer_pointer = POINTER(c_void_p)
OpaqueCFTypeRef = objc.createOpaquePointerType('OpaqueCFTypeRef', '^{OpaqueCFType=}', None)
def value(self):
# CFBundleGetDataPointerForName when used with framework variables (aka constants) returns pointers to the actual objects
# So we need to take that pointer, de-reference it, then convert it back to whatver type it is (usually NSString/CFStringRef)
obj = cast(self.__pointer__, pointer_pointer).contents
return objc.objc_object(c_void_p=obj)
# attach it to the class
OpaqueCFTypeRef.value = types.MethodType(value, None, OpaqueCFTypeRef)
# pyobjc doesn't like loading CFBundleGetDataPointerForName for us
CoreFoundation = NSBundle.bundleWithPath_('/System/Library/Frameworks/CoreFoundation.framework')
objc.loadBundleFunctions(CoreFoundation, globals(), [('CFBundleGetDataPointerForName', '^{OpaqueCFType=}@@')])
def frameworkVariable(bundle_path, vname):
# Trying to use CFBundleGetDataPointerForName with NSBundle loaded bundles doesn't seem to work, so let's use CFBundle
b_dir = NSURL.fileURLWithPath_isDirectory_(bundle_path, True)
bundle = CFBundleCreate(None, b_dir)
return CFBundleGetDataPointerForName(bundle, vname).value()
frameworkVariable('/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework', 'kMDSPreferencesName')

... but sometimes you'll find values that you get hits for, but none of these 3 methods are able to load the value.

That's because a variable can be flagged as externally available or not.

If you want to see a list of all the symbols, available externally or not, you can use lldb:

 lldb /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Metadata -o 'ta modules dump symtab' -o 'quit'

Looking through the output, you see:

Symtab, file = /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Metadata, num_symbols = 4552:
              Debug symbol
              |Synthetic symbol
              ||Externally Visible
              |||
Index   UserID DSX Type            File Address/Value Load Address       Size               Flags      Name
------- ------ --- --------------- ------------------ ------------------ ------------------ ---------- ----------------------------------
...
[ 3674]   3674   X Data            0x00000000008a4e58                    0x0000000000000008 0x000f0000 kMDSPreferencesName

That "X" = Externally Visible

Other symbols, however, you may not see that X:

lldb /System/Library/Frameworks/OpenDirectory.framework/Frameworks/CFOpenDirectory.framework/CFOpenDirectory -o 'ta modules dump symtab' -o 'quit'
Symtab, file = /System/Library/Frameworks/OpenDirectory.framework/Frameworks/CFOpenDirectory.framework/CFOpenDirectory, num_symbols = 1289:
               Debug symbol
               |Synthetic symbol
               ||Externally Visible
               |||
Index   UserID DSX Type            File Address/Value Load Address       Size               Flags      Name
------- ------ --- --------------- ------------------ ------------------ ------------------ ---------- ----------------------------------
...
[  389]    389     Data            0x000000000001df80                    0x0000000000000008 0x001e0000 kODKeyConfigNodeName

With a tool like Hopper, however, you can look up the value.

                     _kODKeyConfigNodeName:
000000000000c838         db  0xa8 ; '.'                                         ; DATA XREF=+[ODConfiguration configurationWithDictionary:session:]+202, -[ODConfiguration configuration]+191
000000000000c839         db  0xd0 ; '.'
000000000000c83a         db  0x00 ; '.'
000000000000c83b         db  0x00 ; '.'
000000000000c83c         db  0x00 ; '.'
000000000000c83d         db  0x00 ; '.'
000000000000c83e         db  0x00 ; '.'
000000000000c83f         db  0x00 ; '.'

Looking at the bottom of Hopper, you see this is Segment __DATA, section __const

The data for that entry shows: a8d0000000000000, which translates to the address 000000000000d0a8

Going to address 0xd0a8 in Hopper reveals:

                     cfstring_node_name:
000000000000d0a8         dq         0x0000000000020558, 0x00000000000007c8, 0x0000000000009891, 0x0000000000000009 ; "node name"

Cool - we learned that the string value for the constant kODKeyConfigNodeName is "node name", even though it's not externally available.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment