Skip to content

Instantly share code, notes, and snippets.

@thomasfinch
Created September 12, 2016 15:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save thomasfinch/47c8b5880f953b102f6d6ef5620d6853 to your computer and use it in GitHub Desktop.
Save thomasfinch/47c8b5880f953b102f6d6ef5620d6853 to your computer and use it in GitHub Desktop.
TBD dumper, able to pull frameworks from a connected device and dump TBDs to the private frameworks folder automatically
#!/usr/local/bin/python
# Dumping all frameworks requires dsc_extractor, idk where to get it
import os, sys, shutil
device_ip = 'localhost'
device_port = '2222'
def dumpTBD(frameworkPath):
tdbStr = '---\n'
tdbStr += 'archs: [ armv7, armv7s, arm64 ]\n' # all the archs cause why not
tdbStr += 'platform: ios\n'
tdbStr += 'install-name: ' + os.popen('otool -l '+frameworkPath+' 2>/dev/null | grep LC_ID_DYLIB -A 2').read().split()[-3] + '\n'
tdbStr += 'current-version: ' + '.'.join(os.popen('otool -l '+frameworkPath+' 2>/dev/null | grep LC_SOURCE_VERSION -A 2').read().split()[-1].split('.')[:3]) + '\n'
tdbStr += 'exports: \n'
tdbStr += ' - archs: [ armv7, armv7s, arm64 ]\n'
symbolOutput = os.popen('nm '+frameworkPath).readlines()
symbolOutput = [' '.join(l.split()[2:]) for l in symbolOutput] # clean up output
# Find all objc ivars
ivars = []
for symbol in symbolOutput:
if symbol.startswith('_OBJC_IVAR_$'):
ivars.append(symbol.replace('_OBJC_IVAR_$', ''))
symbolOutput = [s for s in symbolOutput if not s.startswith('_OBJC_IVAR_$')]
# Find all objc classes
classes = []
for symbol in symbolOutput:
if symbol.startswith('_OBJC_CLASS_$') or symbol.startswith('_OBJC_METACLASS_$'):
classes.append(symbol.replace('_OBJC_CLASS_$', '').replace('_OBJC_METACLASS_$', ''))
symbolOutput = [s for s in symbolOutput if not s.startswith('_OBJC_CLASS_$') and not s.startswith('_OBJC_METACLASS_$')]
# Find all symbols by filtering remaining output
ignoredKeywords = ['-[', '+[', '___', '.cxx_destruct', 'GCC_except_table', '_der_', '__der_', '_objc_']
symbolOutput = [s for s in symbolOutput if not any([s.startswith(i) for i in ignoredKeywords])] # filter out ignored symbols by keyword
symbolOutput = [s for s in symbolOutput if len(s) > 0] # filter out empty symbols
symbols = symbolOutput
# Add symbols to the TBD
tdbStr += ' symbols: [\n'
for i, symbol in enumerate(symbols):
tdbStr += ' '*23 + symbol
if i != len(symbols) - 1:
tdbStr += ',\n'
else:
tdbStr += ' ]\n'
# Add objc classes to the TBD
tdbStr += ' objc-classes: [\n'
for i, className in enumerate(classes):
tdbStr += ' '*23 + className
if i != len(classes) - 1:
tdbStr += ',\n'
else:
tdbStr +=' ]\n'
# Add objc ivars to the TBD
tdbStr += ' objc-ivars: [\n'
for i, ivar in enumerate(ivars):
tdbStr += ' '*23 + ivar
if i != len(ivars) - 1:
tdbStr += ',\n'
else:
tdbStr +=' ]\n'
tdbStr += '...\n'
return tdbStr
def main():
global device_ip, device_port
if len(sys.argv) < 2 or (sys.argv[1] != '-a' and sys.argv[1] != '-d') or (sys.argv[1] == '-d' and len(sys.argv) != 3):
print 'Usage:'
print ' ' + sys.argv[0] + ' -d <path to framework file>'
print ' ' + sys.argv[0] + ' -a'
print '\nOptions:'
print ' -d Create a TBD file from the framework at the given path'
print ' -a Pull all frameworks from a connected device, create TBDs for them, and save to iPhone SDK PrivateFrameworks folder (set device IP and port in ' + sys.argv[0] + ')'
print '\nDumping all frameworks requires dsc_extractor, idk where to get it'
exit(1)
if sys.argv[1] == '-d': # Dump a single framework
print dumpTBD(sys.argv[2])
elif sys.argv[1] == '-a': # Pull all frameworks from device and dump them
cacheExtractDir = 'extractedcache'
privateFrameworksDir = '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/PrivateFrameworks'
# Download and extract the DYLD cache from the device
os.system('scp -P ' + device_port + ' root@'+ device_ip + ':/System/Library/Caches/com.apple.dyld/dyld_shared_cache_armv7s .')
os.system('dsc_extractor dyld_shared_cache_armv7s ./' + cacheExtractDir)
os.system('sudo mkdir ' + privateFrameworksDir)
# Go through all extracted private frameworks, dump them, and save to the SDK private frameworks dir
for frameworkDir in [d[0] for d in os.walk(cacheExtractDir + '/System/Library/PrivateFrameworks/')][1:]:
frameworkName = frameworkDir.split('/')[-1].replace('.framework', '')
# Ignores frameworks with sub-frameworks (like Accessibility) unfortunately
if not os.path.exists(frameworkDir + '/' + frameworkName):
continue
# Dump and copy to the correct folder
tbdStr = dumpTBD(frameworkDir + '/' + frameworkName)
frameworkPath = privateFrameworksDir + '/' + frameworkName + '.framework'
os.system('sudo mkdir ' + frameworkPath + ' 2>/dev/null')
with open('tmpfile', 'w') as tmpfile:
tmpfile.write(tbdStr)
os.system('sudo cp tmpfile ' + frameworkPath + '/' + frameworkName + '.tbd')
# Cleanup
os.system('rm tmpfile')
os.system('rm dyld_shared_cache_armv7s')
shutil.rmtree(cacheExtractDir)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment