Created
September 12, 2016 15:09
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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