jtool - Taking the O out of otool(1), and so much more

What is this?

The jtool utility started as a companion utility to the 1st edition of MacOS internals, because I wanted to demonstrate Mach-O format intrinstics, and was annoyed with XCode's otool(1). Along the way, jtool absorbed additional Mach-O commands such as atos(1), dyldinfo(1), nm(1), segedit(1), pagestuff(1), strings(1) , and even codesign(1) and the informal ldid. Most importantly, it can be run on a variety of platforms - OS X, iOS, and even Linux, where Apple's tools don't exist.

But that's not all. jtool provides many many novel features:

As the code got more and more complex, I decided to rewrite jtool from scratch, bringing you jtool2 - and effectively deprecating the v1 binary. New features in jtool2 include:

jtool and jtool2 ENTIRELY FREE for use of any type (AISE), and the latest version can always be found right here. For the legacy v1 download, click here, which I'm leaving here because I still am not finished with Objective-C support in v2.

4/21/2024 - I no longer support jtool or jtool2, but disarm(j) can do all this, and much, much more.
morpheus@Bifröst (~) %jtool2 --help                                                                                           11:10
Usage: jtool [options] _filename_

OTool Compatible Options:
   -h         	Dump Mach-O (or DYLD Shared Cache) header
   -l         	List sections/commands in binary
   -L         	print shared libraries used

JTool (classic) Options:
   -S         	List Symbols (like NM)
   -v[v]      	Toggle verbosity (vv = very verbose)
   -e         	extract fat slice, Mach-O segment/section, dyld shared cache dylib or (NEW) kernelcache kext
   -q         	Quick operation - do not process any symbols in the Mach-O
   -F         	find all occurrences of _string_ in binary
   -a         	Find offset/segment corresponding to virtual address _addr_
   -o         	Find address corresponding to offset _offset_
   -d         	Dump (smart dump, will disassemble text and dump data by autodetecting)

Code Signing Options:
   --sig      	Show code signature in binary (if any)
   --ent      	Show entitlements in binary (if any)
   -+ent=...[,...]	Inject entitlements into binary (implies resigning inplace)
   -+platformize	Platformize binary (injects platform-application, also implies resigning inplace)

Joker Compatible Options (applicable on kernel caches only):
   -k         	List kexts
   -K         	Kextract™ a kernel extension by its bundle ID
   -dec       	Decompress a kernelcache to /tmp/kernel (no longer necessary since JTool can now operate on compressed caches)

dyldinfo Compatible Options:
   --bind     		print addresses dyld will set based on symbolic lookups
   --lazy_bind		print addresses dyld will lazily set on first use
   --opcodes  		print opcodes used to generate the rebase and binding information
   --function_starts	print table of function start addresses

Newer (JTool 2) Options:
   --analyze  	Analyze file and create a companion file
   --symbolicate	Symbolicate an .ips panic file
   --tbd      	Create a .tbd file (for *OS private frameworks only - you'll need the dyld shared cache for this)
   -D         	Decompile (totally experimental - would love your feedback if you're reading this)
   -G         	Gadget search (specify gadgets as comma delimited mnemonics)

Environment Variables:
   ARCH                	Select architecture slice. Set to arm64, arm64e, arm64_32, armv7, armv7k, x86_64 or (not for long) i386
   JDEBUG              	Enhanced debug output. May be very verbose
   JCOLOR              	ANSI Colors. Note you'll need 'less -R' if piping output
   JTOOLDIR            	path to search for companion jtool files (default: $PWD).
			Use this to force create a file, if one does not exist
   NOPSUP              	Suppress NOPs in disassembly

Important: Jtool has been thoroughly tested, but I still rely on your bug reports to tackle some of the more exotic animals in the Mach-O menagerie. If you have an interesting binary which JTool is struggling with or if it (*gulp*) crashes, PLEASE LET ME KNOW. I can't fix bugs I don't know about.. and ranting on Twitter that you got a segfault is useless because a) I don't check Twitter much b) jtool2 was not meant to consider malicious malformed Mach-Os - it is targeting AAPL's own binaries and c) you're just denigrating a powerful, useful and FREE tool. If you think you can do better, write your own.

So let's go over options, one by one:

There are so many useful features to jtool that I can't put them all in the man page anymore. Truthfully, nobody reads the man anyway (since you have to move jtool.1 to /usr/share/man/man1). So I finally put up a decent HTML document. That, too, is growing out of proportion, so even here not everything is covered, but here's a hyperlinked ToC to what is:

Otool Compatible Options

When I started work on jtool, I thought I would make it a drop-in replacement for otool, so some of its options are in fact identical. Those include -L (to show library dependencies, similar to ldd), and -f (for FAT headers). Others are compatible, but deviate. For example: -h for the Mach-O header, will print the details about the header not on a single line, but on multiple ones:

Zephyr:~ morpheus$ otool -h /bin/ls
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
 0xfeedfacf 16777223          3  0x80           2    19       1816 0x00200085

Zephyr:~ morpheus$ jtool -h  /bin/ls
Magic:	64-bit Mach-O
Type:	executable
CPU:	x86_64
Cmds:	19
size:	1816 bytes
Flags:	0x200085

The reason I deviated is to make jtool more grep(1) friendly, so you could isolate the line containing the attribute you want. In the case of the most common option (-l), however, it actually makes sense to combine the output from otool's multiple lines into less lines, in order to make it more friendly. Case in point:

$ otool -l /bin/ls                $ jtool -l /bin/ls
Load command 0                    LC 00: LC_SEGMENT_64          Mem: 0x000000000-0x100000000      __PAGEZERO
      cmd LC_SEGMENT_64           LC 01: LC_SEGMENT_64          Mem: 0x100000000-0x100005000      __TEXT
  cmdsize 72                             Mem: 0x100004430-0x100004604            __TEXT.__stubs  (Symbol Stubs)
  segname __PAGEZERO                     Mem: 0x100004430-0x100004604            __TEXT.__stubs  (Symbol Stubs)
   vmaddr 0x0000000000000000             Mem: 0x100004920-0x100004b10            __TEXT.__const  
   vmsize 0x0000000100000000             Mem: 0x100004b10-0x100004f66            __TEXT.__cstring        (C-String Literals)               
  fileoff 0                              Mem: 0x100004430-0x100004604            __TEXT.__stubs  (Symbol Stubs)
 filesize 0                        LC 02: LC_SEGMENT_64          Mem: 0x100005000-0x100006000      __DATA
  maxprot 0x00000000                         ....
 initprot 0x00000000
   nsects 0
    flags 0x0
Load command 1
      cmd LC_SEGMENT_64
  cmdsize 552
  segname __TEXT
   vmaddr 0x0000000100000000
   vmsize 0x0000000000005000
  fileoff 0
 filesize 20480
  maxprot 0x00000007
 initprot 0x00000005
   nsects 6
    flags 0x0

otool's output is simply terrible, in that it is thinly spread over multiple lines. jtool condenses it so you can clearly see what matters, and take less space (or scrolling through pages and pages of more(1) or less(1)). Another benefit is using grep:

# Finding details about a segment with otool(1):
Zephyr:~ morpheus$ otool -l /bin/ls | grep LC_SEGM
      cmd LC_SEGMENT_64
      cmd LC_SEGMENT_64
      cmd LC_SEGMENT_64
      cmd LC_SEGMENT_64
# Really helpful, right? Yes, you could do that with grep -A, but I find this simpler: 
Zephyr:~ morpheus$ jtool -l -v /bin/ls | grep LC_SEGM
LC 00: LC_SEGMENT_64          Mem: 0x000000000-0x100000000	File: Not Mapped	---/---	__PAGEZERO
LC 01: LC_SEGMENT_64          Mem: 0x100000000-0x100005000	File: 0x0-0x5000	r-x/rwx	__TEXT
LC 02: LC_SEGMENT_64          Mem: 0x100005000-0x100006000	File: 0x5000-0x6000	rw-/rwx	__DATA
LC 03: LC_SEGMENT_64          Mem: 0x100006000-0x100009000	File: 0x6000-0x8750	r--/rwx	__LINKEDIT

Notice in the above use of -v, which adds verbosity - in the case of LC_SEGMENT commands, the file mappings.

For universal (fat) files, jtool provides the same -arch switch to single out a particular architecture. There are several differences from otool:

The last option there seems unusual - after all, how many architectures can there be in a fat file? Yeah, well, I thought so too, until I encountered TaiG's 8.4 Jailbreak, which not only uses fat binaries of some 26-27 architectures, but also uses duplicate entries - confusing otool since it only matches the first one - but jtool can single out a particular slice this way. (q.v my writeup on that jailbreak here for an example).

dyldinfo(1) compatible options

The dyldinfo(1) utility is really useful to explore the inner workings of Apple's dynamic loader. But it's not available for iOS. jtool can match most of dyldinfo's options easily, and can in fact emulate it busybox-style, if you symbolically link dyldinfo to it and call it:

Zephyr:~ morpheus$ ln -s `which jtool` /tmp/dyldinfo
Zephyr:~ morpheus$ !$
Usage: dyldinfo [-arch <arch>] <options> <mach-o file>
-dylibs           print dependent dylibs
-rebase           print addresses dyld will adjust if file not loaded at preferred address
-bind             print addresses dyld will set based on symbolic lookups
-weak_bind        print symbols which dyld must coalesce
-lazy_bind        print addresses dyld will lazily set on first use
-function_starts  print table of function start addresses
# Call jtool with some dyldinfo option:
Zephyr:~ morpheus$ /tmp/dyldinfo -bind /bin/ls
bind information:
segment section          address        type    addend dylib            symbol
__DATA  __got            0x100005000    pointer      0 libSystem.B.dylib    __DefaultRuneLocale
__DATA  __got            0x100005008    pointer      0 libSystem.B.dylib    ___stack_chk_guard
__DATA  __got            0x100005010    pointer      0 libSystem.B.dylib    ___stderrp
__DATA  __got            0x100005018    pointer      0 libSystem.B.dylib    ___stdoutp
__DATA  __got            0x100005020    pointer      0 libSystem.B.dylib    _optind
__DATA  __nl_symbol_ptr  0x100005028    pointer      0 libSystem.B.dylib    dyld_stub_binder
# Call the real binary:
Zephyr:~ morpheus$ dyldinfo -bind /bin/ls
bind information:
segment section          address        type    addend dylib            symbol
__DATA  __got            0x100005000    pointer      0 libSystem        __DefaultRuneLocale
__DATA  __got            0x100005008    pointer      0 libSystem        ___stack_chk_guard
__DATA  __got            0x100005010    pointer      0 libSystem        ___stderrp
__DATA  __got            0x100005018    pointer      0 libSystem        ___stdoutp
__DATA  __got            0x100005020    pointer      0 libSystem        _optind
__DATA  __nl_symbol_ptr  0x100005028    pointer      0 libSystem        dyld_stub_binder
# Show weak binds: (Thanks Guhyeon)
Zephyr:~ morpheus$ jtool -weak_bind /usr/sbin/weakpass_edit  
bind information:
segment section          address    index  dylib            symbol
__DATA  __la_symbol_ptr  0x100003040           this-image    __ZdlPv
__DATA  __la_symbol_ptr  0x100003048           this-image    __Znwm

Since jtool basically mimics dyldinfo 1:1, refer to the latter's man(1) page for more detail. I use -function_starts, -bind and -lazy_bind most often.

New Options


You may be familiar with the pagestuff(1) command, which dumps the layout of a Mach-O binary. Syntax is odd, placing the filename as the first argument and a bit crude:

morpheus@Bifröst (~) % pagestuff /bin/ls -a  
File Page 0 contains Mach-O headers
File Page 0 contains contents of section (__TEXT,__text)
Symbols on file page 0 virtual address 0x100000f08 to 0x100001000
File Page 1 contains contents of section (__TEXT,__text)
Symbols on file page 1 virtual address 0x100001000 to 0x100002000
File Page 2 contains contents of section (__TEXT,__text)
Symbols on file page 2 virtual address 0x100002000 to 0x100003000
File Page 3 contains contents of section (__TEXT,__text)
Symbols on file page 3 virtual address 0x100003000 to 0x100004000
...removed plentiful, hard-to-read output

jtool --pages provides (what I believe to be) nicer output, like so:

# Cut to the chase:
imorpheus@Bifröst (~) % jtool2   --pages /bin/ls
0x0-0x5000 __TEXT	(20480 bytes)
	0xf08-0x4420 __TEXT.__text	(13592 bytes)
	0x4420-0x45e8 __TEXT.__stubs	(456 bytes)
	0x45e8-0x48f0 __TEXT.__stub_helper	(776 bytes)
	0x48f0-0x4ae8 __TEXT.__const	(504 bytes)
	0x4ae8-0x4f67 __TEXT.__cstring	(1151 bytes)
	0x4f68-0x4ff8 __TEXT.__unwind_info	(144 bytes)
0x5000-0x6000 __DATA	(4096 bytes)
	0x5000-0x5010 __DATA.__nl_symbol_ptr	(16 bytes)
	0x5010-0x5038 __DATA.__got	(40 bytes)
	0x5038-0x5298 __DATA.__la_symbol_ptr	(608 bytes)
	0x52a0-0x54c8 __DATA.__const	(552 bytes)
	0x54d0-0x54f8 __DATA.__data	(40 bytes)
0x6000-0x9730 __LINKEDIT	(14128 bytes)
	0x6000-0x6018 Rebase info       (opcodes)	(24 bytes)
	0x6018-0x6090 Binding info      (opcodes)	(120 bytes)
	0x6090-0x65f0 Lazy Binding info (opcodes)	(1376 bytes)
	0x6610-0x6648 Function Starts	(56 bytes)
	0x6648-0x6670 Data In Code	(40 bytes)
	0x6670-0x6bb0 Symbol Table	(1344 bytes)
	0x6e2c-0x71fc String Table	(976 bytes)
	0x7200-0x9730 Code Signature	(9520 bytes)
# -v for mappings:
morpheus@Bifröst (~) % jtool2  -v  --pages /bin/ls                                                                               0x0-0x5000 0x100000000-0x100005000 __TEXT	(20480 bytes)
	0xf08-0x4420 0x100000f08-0x100004420 __TEXT.__text	(13592 bytes)
	0x4420-0x45e8 0x100004420-0x1000045e8 __TEXT.__stubs	(456 bytes)
	0x45e8-0x48f0 0x1000045e8-0x1000048f0 __TEXT.__stub_helper	(776 bytes)
	0x48f0-0x4ae8 0x1000048f0-0x100004ae8 __TEXT.__const	(504 bytes)
	0x4ae8-0x4f67 0x100004ae8-0x100004f67 __TEXT.__cstring	(1151 bytes)
	0x4f68-0x4ff8 0x100004f68-0x100004ff8 __TEXT.__unwind_info	(144 bytes)
0x5000-0x6000 0x100005000-0x100006000 __DATA	(4096 bytes)
	0x5000-0x5010 0x100005000-0x100005010 __DATA.__nl_symbol_ptr	(16 bytes)
	0x5010-0x5038 0x100005010-0x100005038 __DATA.__got	(40 bytes)
	0x5038-0x5298 0x100005038-0x100005298 __DATA.__la_symbol_ptr	(608 bytes)
	0x52a0-0x54c8 0x1000052a0-0x1000054c8 __DATA.__const	(552 bytes)
	0x54d0-0x54f8 0x1000054d0-0x1000054f8 __DATA.__data	(40 bytes)
0x6000-0x9730 0x100006000-0x100009730 __LINKEDIT	(14128 bytes)
	0x6000-0x6018 0x100006000-0x100006018 Rebase info       (opcodes)	(24 bytes)
	0x6018-0x6090 0x100006018-0x100006090 Binding info      (opcodes)	(120 bytes)
	0x6090-0x65f0 0x100006090-0x1000065f0 Lazy Binding info (opcodes)	(1376 bytes)
	0x6610-0x6648 0x100006610-0x100006648 Function Starts	(56 bytes)
	0x6648-0x6670 0x100006648-0x100006670 Data In Code	(40 bytes)
	0x6670-0x6bb0 0x100006670-0x100006bb0 Symbol Table	(1344 bytes)
	0x6e2c-0x71fc 0x100006e2c-0x1000071fc String Table	(976 bytes)
	0x7200-0x9730 0x100007200-0x100009730 Code Signature	(9520 bytes)


Sometimes you're just interested in a quick lookup of an offset to an address, without having to sift through pagestuff(1) or jtool --pages output. That's where -a comes in:

morpheus@Bifröst (~) % jtool2 -a 0x100040050 /Volumes/PeaceD16D57.D321OS/sbin/launchd
Address 0x100040050 (offset 0x40050) is in __DATA.__auth_got.. and linked to symbol libSystem.B.dylib::___stack_chk_fail
# Reverse lookup, from offset to address:
morpheus@Bifröst (~) % jtool2 -o 0x355 /Volumes/PeaceD16D57.D321OS/sbin/launchd
Offset 0x355 is in __DATA.__common, loaded at address 0x100044b55

With -o doing the offset to address mapping.


-S does the same thing that nm(1) does. The option is simply -S (uppercase) and -v for full info (as in, which dylib the symbol is in). For nm, that -v is -m. Unlike dyldinfo, the options aren't compatible.

What's really useful here is the quick scriptability, like if you're looking for a particular symbol across many binaries:

# If something calls SecTaskCopyValueForEntitlement, then it checks entitlements:
# (though there are other ways, like calling csops[_audittoken] directly..)
Phontifex-Magnus:/usr/libexec root# for d in /usr/libexec/* ; do  \
     if jtool -S $d 2>/dev/null | grep SecTaskCopy > /dev/null; then \
        echo $d checks entitlements...; \
     fi;  \
/usr/libexec/OTATaskingAgent checks entitlements...
/usr/libexec/adid checks entitlements...
/usr/libexec/configd checks entitlements...
/usr/libexec/crash_mover checks entitlements...
/usr/libexec/demod checks entitlements...
/usr/libexec/demod_helper checks entitlements...
/usr/libexec/keybagd checks entitlements...
/usr/libexec/locationd checks entitlements...
/usr/libexec/lockbot checks entitlements...
/usr/libexec/lskdd checks entitlements...
/usr/libexec/lskdmsed checks entitlements...
/usr/libexec/mobile_obliterator checks entitlements...
/usr/libexec/mobileassetd checks entitlements...
/usr/libexec/nlcd checks entitlements...
/usr/libexec/pfd checks entitlements...
/usr/libexec/pkd checks entitlements...
/usr/libexec/rolld checks entitlements...
/usr/libexec/securityd checks entitlements...
/usr/libexec/timed checks entitlements...
/usr/libexec/transitd checks entitlements...
/usr/libexec/webinspectord checks entitlements...

Well, not quick, maybe (it grows on you), but certainly efficient.

(Luca, you can finally ln -s ......jtool2 /usr/bin/nm :-)


Find a string in a file. -v will also show you the string, highlighted if part of a larger string:

morpheus@Bifröst (~) % jtool2 -F launchd /Volumes/PeaceD16D57.D321OS/sbin/launchd | head -5
morpheus@Bifröst (~) % jtool2 -v -F launchd /Volumes/PeaceD16D57.D321OS/sbin/launchd | head -5
0x100044a42:0x242:__DATA.__common - __launchd
0x100044bd6:0x3d6:__DATA.__common - __dof_launchd
0x100032d9e:0x32d9e:__TEXT.__cstring - ...; root:libxpc_executables-1336.240.2~4/launchd/RELEASE_ARM64E
0x100032e2f:0x32e2f:__TEXT.__cstring - ...; root:libxpc_executables-1336.240.2~4/launchd/RELEASE_ARM64E
0x100032e9a:0x32e9a:__TEXT.__cstring - ...ies/libxpc_executables/install/Symbols/launchd
# SO useful for user mode entitlement searches:
jtool2 -v -F com.apple.private.alloy.coreduet /Volumes/PeaceD16D57.D321OS/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64e
0x180f184b7:0xf184b7:CoreFoundation:__TEXT - com.apple.private.alloy.coreduet
0x18ca02c6a:0xca02c6a:IDS:__TEXT - com.apple.private.alloy.coreduet
0x1a05f1cf9:0x205f1cf9:PowerlogLiteOperators:__TEXT - com.apple.private.alloy.coreduet
0x1a8c8c8e8:0x28c8c8e8:PowerlogHelperdOperators:__TEXT - com.apple.private.alloy.coreduet
0x1ac971dfe:0x2c971dfe:KnowledgeMonitor:__TEXT - com.apple.private.alloy.coreduet

Kernel Caches (v2)


The joker(j) tool is effectively deprecated. It can now all be done with --analyze:

# New kernelcaches have no symbols:
morpheus@Bifröst (~) % jtool2 -S ~/Documents/iOS/13/kernelcache.release.iphone11
# Run analysis, generating companion file:
morpheus@Bifröst (~) % jtool2 --analyze ~/Documents/iOS/13/kernelcache.release.iphone11
Analyzing kernelcache..
This is a new-style A12 kernelcache (Darwin Kernel Version 19.0.0: Thu Jun 27 20:08:29 PDT 2019; root:xnu-6153.
-- Processing __TEXT_EXEC.__text..
Disassembling 23244376 bytes from address 0xfffffff007b34000 (offset 0xb30000):
__ZN11OSMetaClassC2EPKcPKS_j is 0xfffffff008067248 (OSMetaClass)
Can't get IOKit Object @0x0 (0xfffffff008c007bc)
Can't get IOKit Object @0x0 (0xfffffff00914b890)
Analyzing __DATA.__data..
Got _localnode_id @0xfffffff009198620
Analyzing __DATA.__sysctl_set..
Analyzing fuctions...
Can't get realhost :-(
Analyzing __DATA_CONST.. (1st pass)
*** Got non zero value (0xfffffff007b89fc4) for non-pointer in sched struct - Please Tell J!
-- Note: The memory_entry MIG subsytem contains more messages (3) than I expected (2)
LAST ARG0 : fffffff007737fcc , fffffff007401370, 7eba
last Arg2 is not 0?
processing flows...
Analyzing __DATA_CONST.. (2nd pass)
*** Got non zero value (0xfffffff007b89fc4) for non-pointer in sched struct - Please Tell J!
Analyzing __PPLTEXT.__text..
opened companion file ./kernelcache.release.iphone11.ARM64.A59AC05A-2E67-3889-B6D7-ECC19A543C50
Dumping symbol cache to file
Symbolicated 4480 symbols and 85749 functions

The companion file is intentionally designed to be readable and editable, unlike dSyms or databases which may get corrupted. When you next get them symbols from Lumina(TM), remember where they must have been sourced from..

morpheus@Bifröst (~) % wc -l kernelcache.release.iphone11.ARM64.A59AC05A-2E67-3889-B6D7-ECC19A543C50
   90229 kernelcache.release.iphone11.ARM64.A59AC05A-2E67-3889-B6D7-ECC19A543C50
morpheus@Bifröst (~) % tail kernelcache.release.iphone11.ARM64.A59AC05A-2E67-3889-B6D7-ECC19A543C50
0xfffffff0093cd780|__ZN30AppleBCMWLANCoreFirmwareLoader10gMetaClassE|Rule 800
0xfffffff0093cddf8|__ZN21AppleBCMWLANWorkOrder10gMetaClassE|Rule 824
0xfffffff0093cde40|__ZN27AppleBCMWLANTxCommandBuffer10gMetaClassE|Rule 829
0xfffffff0093cde68|__ZN23AppleBCMWLANPacketQueue10gMetaClassE|Rule 830
0xfffffff0093cded8|__ZN27AppleBCMWLANQueueDescriptor10gMetaClassE|Rule 835

Panic logs

--symbolicate with crash log supplied, assuming companion file from --analyze was created. You know. Like all some high priced productz do (often, poorly), only free.

DYLD Shared Caches

Apple prelinks most dylibs and plugins into a "Shared Library Cache". The SLC is located in /var/db/dyld (OS X) and /System/Library/Caches/com.apple.dyld (iOS). The OS X cache also has a "map", but the iOS one doesn't have a map.

In iOS, there are no free floating dylibs - it's all in the cache. This makes caches very important, and there are quite a few "decaching" tools. JTool doesn't offer comprehensive support, only the stuff I find useful. In particular:

# Identifying a shared cache file
Phontifex-Magnus:~ root# jtool  /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
File is a shared cache containing 1007 images (use -l to list)
# Finding the map of a shared cache file (formerly, --map which isn't needed anymore
Phontifex-Magnus:~ root# jtool -l /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
File is a shared cache containing 1007 images
   0:        180028000 /System/Library/AccessibilityBundles/AXSpeechImplementation.bundle/AXSpeechImplementation
   1:        180030000 /System/Library/AccessibilityBundles/AccessibilitySettingsLoader.bundle/AccessibilitySettingsLoader
   2:        18003c000 /System/Library/AccessibilityBundles/AccountsUI.axbundle/AccountsUI
   3:        180040000 /System/Library/AccessibilityBundles/AddressBookUIFramework.axbundle/AddressBookUIFramework
   4:        180048000 /System/Library/AccessibilityBundles/CameraKit.axbundle/CameraKit
   5:        180058000 /System/Library/AccessibilityBundles/CameraUI.axbundle/CameraUI
   6:        180064000 /System/Library/AccessibilityBundles/HearingAidUIServer.axuiservice/HearingAidUIServer
   7:        180074000 /System/Library/AccessibilityBundles/MapKitFramework.axbundle/MapKitFramework
   8:        180080000 /System/Library/AccessibilityBundles/MediaPlayerFramework.axbundle/MediaPlayerFramework
# And the -h (header) option, I added after Pangu 9. See anyhing different, boys and girls? *hint* *hint*
Phontifex-Magnus:~ root# jtool -h /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64           
File is a shared cache containing 1007 images (use -l to list)
6 mappings starting from 152, Mapping Count: 1007. 344 Images starting from 0
DYLD base address: 0, Code Signature Address: 25a4c000 (2f0f02 bytes)
Slide info: 1ca18000 (1a4000 bytes)
Local Symbols: 204c8000 (5584000 bytes)
mapping r--/r--    0MB        180000000 -> 180028000         (25d40000-25d68000)
mapping r-x/r-x  384MB        180028000 -> 1980a4000         (28000-180a4000)
mapping rw-/rw-   73MB        19a0a4000 -> 19ea18000         (180a4000-1ca18000)
mapping r--/r--   12MB        1a0a18000 -> 1a16b0000         (1ca18000-1d6b0000)
mapping r--/r--    0MB        1a16b0000 -> 1a16b4000         (25d68000-25d6c000)
mapping r--/r--   46MB        1a16b4000 -> 1a44c8000         (1d6b4000-204c8000)

Jtool can easily decache files - simply use -e dylib on a cache file to get your dylib. Note that the extracted dylib will be big - circa 50MB - because JTool does not deconstruct the merged __LINKEDIT segment.

But why extract??? A key feature of Jtool that other decachers do not have, is its ability to work on a dylib while still in the cache! This not only saves you disk space, but also allows you to see how cached dylibs interact with eachother (e.g. cross dylib calls). To use this feature, simply specify ":" as a delimiter between the cache and the dylib name. All the standard features (e.g. -l, -S, etc), work, but the really useful feature is -d. For example:

root@phontifexMagnus (~)# jtool -d /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64:libMobileGestalt | more
Found but not extracting - setting File Start to 16a10000
Disassembling from file offset 0x11a4, Address 0x196a111a4 
Processing cached file from offset 16a38000  size: 25d44000
   196a111a4    ADRP   x8, 32640                ; ->R8 = 0x19e991000 
   196a111a8    STR    X0, [X8, #280]           ; *0x19e991118 = X0 ARG0
   196a111ac    RET                     
   196a111b0    ORR    W1, WZR, #0x1            ; ->R1 = 0x1 
   196a111b4    B      _func_196a111b8  ; 196a111b8

Objective-C Support

jtool now recognizes objective-C. Use "-d objc" to get a list of classes:

# Use with -v for more detail, without just the classlist
Zephyr:~ morpheus$  jtool -d objc ~/Documents/RE/SpringBoard9.2 

Using -v here will actually reverse the classes, a la classdump-Z, though with a bit more detail, and - as usual - color :-). You can also specify a classname as the argument to -d, and jtool is smart enough to figure out you want to reverse it:

(Yes, I know that the protocols aren't demangled yet - I'm working on that)

AND you can then use -d to get to a particular method. To get buy the horrid +/-/[/spaces, you can use C++ like syntax:

Note that this is done by reconstructing the classes without linking with Apple's Objective-C library so it also works on the Linux version! However, because I do the class walking by myself it might be A) still a bit slow (Springboard is a prime example, with countless classes) and B) possibly buggy. You can use NOOBJC=1 to disable objective-c, but please let me know if you encounter bugs here.

Darn Cool Options

MIG Detection (v0.98.9999 02/02/2016)

No special switches for this one - If jtool detects you're dumping a __DATA.__const segment of a MIG enabled binary (e.g. the kernel or one of the many daemons of /usr/libexec), it will automatically ferret out the MIG dispatch tables. In full color, too :-)

You can try this on the OS X kernel for fun (that's /System/Library/Kernels/kernel), but doing so on the iOS kernel is even more useful. That said, there's also Joker for that.

Misc. Features (Jan 2017)

  • Significant speedup when companion files are detected
  • More robust OBJ-C handling - won't crash as much. And please see note below on reporting crashes!
  • New option: -D: to decompile - like -d, but will only show "; " lines, i.e. lines that Jtool decompiled. You could always do that with grep(1), but color got confusing.

    Can your IDA do this:

  • (err... maybe some IDAPython can do it. Dunno. But jtool does it for free :-)
  • Forget extracting from the Shared Library Cache! jtool can now resolve all symbols in the cache, even when they are in a different dylib! This is super useful because AAPL's reconstructed libraries (in ~/Library/Developer/Xcode/*DeviceSupport) lose external symbols and mess up some __DATA refs. Note that SLC files still have private symbols <redacted>. Working on it.
  • Stack emulation

    Emulating the stack for functions is something I've been putting off for a long time. But recently I've decided it's time.. And by only following STR/STP I've managed to go a reasonably long way: jtool can now detect blocks created on the fly, and tell you what the function they contain is. For example, dispatch_[a]sync or xpc_connection_set_event_handler:

    Better still, it can follow mach_msg construction on the fly. This is really super useful - One of my @TODOs for MOXiI Vol.1 is to document all of SpringBoard, Backboard, and the other daemon's MIGs. You can see how much time it saves me to just do this:

    # Note that:
    # A) you need to disable color for the regexp to work (because of curses sequences)
    # B) the regexp is then "mach_msg(" (decompiled function) or "begins with _" (function label)
    # C) jtool does everything in the cache, no need to extract!
    morpheus@Zephyr (~/Documents/Work/JTool) % JCOLOR=0 jtool -d dyld_shared_cache_arm64:BackBoardServices |
    |                                           egrep "(_mach_msg\(|^_)" | less
    _BKSRestartActionOptionsDescription: # No mach_msg here 
    _BKSTouchDeliveryPolicyServerGetProxyWithErrorHandler: # No mach_msg here either..
    ; _mach_msg(6000000)
    ; _mach_msg(6000001)
    ; _mach_msg(6000002)
    ; _mach_msg(6000003)
    morpheus@Zephyr (~/Documents/Work/JTool) % jtool -d dyld_shared_cache_arm64:BackBoardServices |
                                                egrep "(_mach_msg\(|^_)"       
    ; _mach_msg(6000000)
    ; _mach_msg(6000001)
    ; _mach_msg(6000002)
    ; _mach_msg(6000056)
    ; _mach_msg(6000057)
    ; _mach_msg(0)  # OK, so it's not perfect -- I don't follow FP operations (yet)!
    ; _mach_msg(6000059)
    ; _mach_msg(6000059)
    ; _mach_msg(6000061)
    ; _mach_msg(6001000)
    ; _mach_msg(6001001)

    decompilation improvements

    Functions can now be defined with any argument, and jtool will easily decompile them for you! This has actually been around for a few months now, but I never documented it... And I can't even begin to extol how useful this is when reversing:

    So you can now define your functions in the companion file with parentheses and one letter per argument, denoting type:

    In-cache symbolication

    jtool has long been able to work on dylibs inside the shared cache - and actually has the ability to symbolicate references which would otherwise be lost when extracting - no matter how sophisticated your "decache" script is, as soon as the file is outside the cache any references to other files still in the cache are effectively lost. AAPL keeps modifying the various ways calls can be made - either by resolved stubs, direct calls to the function or (as of DYLD-5xx) "branch islands". But jtool supports them all.

    Code Signing Options

    Code signing options are, IMHO, the 2nd most useful feature of JTool. With iOS security revolving around code signatures and entitlements, it's important to have a way to quickly determine what given entitlements a binary possesses and how it is signed. OS X has codesign(1), but I find it crude (at best) - and what more there's no port to iOS, where it's really necessary.


    The --sig option allows you to validate or display a code signature. With no more arguments, it will display the signature blobs, and perform silent validation. jtool2 presents cleaner output than its predecessor, and suppresses the hashes (since there are potentially hundreds) even when using -v:

    morpheus@Bifröst (~) % jtool2 -v --sig /Volumes/PeaceD16D57.D321OS/System/Library/CoreServices/SpringBoard.app/SpringBoard
    An embedded signature of 114450 bytes, and 4 blobs
    	Blob 0: Type: 0 @44: Code Directory (96046 bytes)
    		Version:     20400
    		Flags:       adhoc  (0x2)
    		CodeLimit:   0xbb0110
    		Identifier:  com.apple.springboard (@0x58)
    		Executable Segment: Base 0x00000000 Limit: 0x00000000 Flags: 0x00000000
    		CDHash:	     88cfeafb874472a197beab3a02717b5e8e799bf41c9e0418511bece8566069a6 (computed)
    		# of Hashes: 2993 code + 5 special
    		Hashes @270 size: 32 Type: SHA-256
    	Blob 1: Type: 2 @96090:  Empty requirement set (12 bytes)
    	Blob 2: Type: 5 @96102: Entitlements (18340 bytes) (use --ent to view)
    	Blob 3: Type: 10000 @114442: Blob Wrapper (8 bytes) (0x10000 is CMS (RFC3852) signature)

    If you really must display the hashes, use -vv.


    If you want to examine the entitlements claimed by a binary, all it takes it --ent. Same as codesign -d --ent, but more accurate, because the latter also presents the blob header (as junk <FA><DE>qq^@^@)^S):

    Phontifex-Magnus:~ root# jtool  --ent /System/Library/CoreServices/SpringBoard.app/SpringBoard 
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    	# ....

    jtool2 also support SimPLISTic format, if you use --ent with -v. This is really useful for heavily entitled binaries - again, taking SpringBoard as an example:

    morpheus@Bifröst (~) % jtool2 -v --ent /Volumes/PeaceD16D57.D321OS/System/Library/CoreServices/SpringBoard.app/SpringBoard
    	allow-obliterate-device:  true
    	application-identifier: com.apple.springboard
    	aps-connection-initiate:  true
    	backupd-connection-initiate:  true
    	checklessPersistentURLTranslation:  true
    	com.apple.BTServer.allowRestrictedServices:  true
    	com.apple.BTServer.programmaticPairing:  true
    	com.apple.CallHistory.sync.allow:  true

    Once again, this is designed with shell scripting and grep(1) in mind, so you can do a for file in /usr/libexec/*; do jtool --ent -v $file; done or other pattern, to get all those undocumented entitlements used by Apple all over the place (That's how I created the Entitlement database.

    --sign (v1)

    The --sign option is an option I've had for a long time internally, and finally released. It is meant to self-sign binaries much in the same way as Saurik's ldid. As with ldid, you can specify an entitlements file to be inserted (using --ent filename). The result is designed to be indistinguishable, once you get past the (temporary) warning/disclaimer:

    morpheus@Zephyr (~)$  jtool --sign binary
    Warning: Destructive option. Output (397920 bytes) written to out.bin
    morpheus@Zephyr ()$  ldid -S binary
    morpheus@Zephyr ()$  ls -l binary out.bin
    -rwxr-xr-x  1 root  staff  397920 Oct 18 03:30 binary
    -rwxr-xr-x  1 root  staff  397920 Oct 18 03:30 out.bin
    morpheus@Zephyr ()$  diff out.bin binary
    morpheus@Zephyr ()$  echo $?

    Rather than using codesign(1) or codesign_allocate(1) internally, jtool performs the entire sequence step by step, so you can see for yourself how code signing ensues - using JDEBUG=1:

    morpheus@Zephyr (~)$ JDEBUG=1 ARCH=armv7 jtool  --sign --ent ent.xml  /tmp/a
    Very last section ends at 0xc11c, so that's where the code signature will be
    Aligning to 16 byte offset - 0xc120
    Allocating Load Command
    First section offset is 7ea4; Mach header size is 580
    Patching header to reflect inserted command @580
    Patching __LINKEDIT to reflect new size of file
    Setting LC fields
    Allocating code signature superblob of 669 bytes, with 3 sub-blobs..
    Setting LC_CODE_SIGNATURE's blob size to match CS Blob size..
    Creating Code Directory with 13 code slots and 5 special slots
    Calculating Hashes to fill code slots..
    Need to pad 288 bytes to page size in last page (because code signature is also in this page)
    Padding to page size with 3808 bytes
    Calculating (modified) last page hash
    Adding empty requirements set to 447
    Filling the special slot (-2) for requirements blob...
     Copying entitlements blob to 459
    Filling the special slot (-5) for entitlement blob...
     Crafting New Mach-O
    Inserting 669 bytes Blob at 49440, bringing new file size to 50109
    Warning: Destructive option. Output (50109 bytes) written to out.bin

    Code signing using Security.framework APIs (MacOS)

    jtool (on MacOS) now uses security framework APIs if you specify --sident (not to be confused with --ident, for the Code Directory Identity) , and specify a partial name or Apple ID or Team ID.

    There's much more, but I'll pick this up later. If you use jtool and find bugs, don't just complain silently - LET ME KNOW and I'll be glad to fix. Likewise if you have any suggestions for improvement. Remember - I build the functionality around my own use cases - and I'll be happy to adjust if you have any others.
    And I don't care if you use IDA or IDA can do this (if they can't, they'll adopt this in their next version) or there's IDAPython, or whatever. This is the tool I use, I find it useful, and I'm encouraging others to use it (freely, unlike IDA/Hopper and their ilk) and suggest improvements.

    More to come:

    Frequently Asked Questions