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:
- in-binary search functionality
- symbol injection
- built-in disassembler functionality with (limited but constantly improving) emulation capabilities, which already outdo fancy commercial GUI disassemblers.
- Color terminal output, enabled by
JCOLOR=1
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:
--analyze
to automatically analyze any Mach-O, generating a companion file.- kernelcache symbolication (what I formerly provided via
joker
) - which has become even more important since the advent of monolithic ("1469") kernelcaches, with no more symbols.jtool2
finds syscalls, Mach traps, MIG tables, interesting (for me, at least) functions, and IOKit objects - thousands of objects in all. - Panic log symbolication: *OS panic logs are JSON and have little to no symbols - but
--symbolicate
(with a companion file prebuilt by--analyze
) will rectify that.
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.
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
- Otool Compatible options
- dyldinfo Compatible options
- Advanced options:
- Code Singing options
- Objective-C
- Darn Cool Options
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
/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 /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
:
jtool
will refuse to touch a fat binary unless you specify the architecture- You can specify the architecture with
-arch
, or specifyARCH=
as an environment variable. This makes it useful to set a default. - You can specify an architecture by number
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$ !$
/tmp/dyldinfo
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
--pages
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)
-a
/-o
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
-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; \ done /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 finallyln -s ......jtool2 /usr/bin/nm
:-)
-F
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 0x100044a42:0x242:__DATA.__common 0x100044bd6:0x3d6:__DATA.__common 0x100032d9e:0x32d9e:__TEXT.__cstring 0x100032e2f:0x32e2f:__TEXT.__cstring 0x100032e9a:0x32e9a:__TEXT.__cstring 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)
Symbolication
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.0.13.132.4~1/RELEASE_ARM64_T8020) -- 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
0xfffffff0093cbb38|ksyn_waitq_element|
0xfffffff0093cd780|__ZN30AppleBCMWLANCoreFirmwareLoader10gMetaClassE|Rule 800
0xfffffff0093cddf8|__ZN21AppleBCMWLANWorkOrder10gMetaClassE|Rule 824
0xfffffff0093cde40|__ZN27AppleBCMWLANTxCommandBuffer10gMetaClassE|Rule 829
0xfffffff0093cde68|__ZN23AppleBCMWLANPacketQueue10gMetaClassE|Rule 830
0xfffffff0093cded8|__ZN27AppleBCMWLANQueueDescriptor10gMetaClassE|Rule 835
0xfffffffc01086f3c|__ZNK11OSSerialize12getMetaClassEv|
0xfffffffffffffde0|_cfil_ctl_disconnect|
0xfffffffffffffe50|_flow_divert_send|
0xfffffffffffffe50|_flow_divert_disconnect|
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
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
_MGSetLogHandler:
196a111a4 ADRP x8, 32640 ; ->R8 = 0x19e991000
196a111a8 STR X0, [X8, #280] ; *0x19e991118 = X0 ARG0
196a111ac RET
_func_196a111b0:
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 SBNotificationCenterHeaderView SBNotificationCenterSectionInfo SBAppSwitcherStatusBarViewCache SBReachabilityManager SBBannerToNotificationCenterGestureHandler SBAppSwitcherSoftOutlineShadowView SBLegibilitySettings SpringBoard SBTransientActiveInterfaceOrientationRequester SBTelephonyAirplaneModeAlertItem SBUIController SBApplicationIcon SBAppSwitcherServiceManager SBInterfaceItemInfo SBItemInfoLayoutCache SBSectionInfo SBRowInfo
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
You can try this on the OS X kernel for fun (that's
Misc. Features (Jan 2017)
Can your IDA do this:

(err... maybe some IDAPython can do it. Dunno. But
jtool
does it for free :-)
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.. __BKSHIDGetBacklightFactor: ; _mach_msg(6000000) __BKSHIDSetBacklightFactorPending: ; _mach_msg(6000001) __BKSHIDSetBacklightFactorWithFadeDuration: ; _mach_msg(6000002) __BKSHIDSetBacklightFactorWithFadeDurationAsync: ; _mach_msg(6000003) .. morpheus@Zephyr (~/Documents/Work/JTool) % jtool -d dyld_shared_cache_arm64:BackBoardServices | egrep "(_mach_msg\(|^_)" _BKSRestartActionOptionsDescription: _BKSTouchDeliveryPolicyServerGetProxyWithErrorHandler: __BKSHIDGetBacklightFactor: ; _mach_msg(6000000) __BKSHIDSetBacklightFactorPending: ; _mach_msg(6000001) __BKSHIDSetBacklightFactorWithFadeDuration: ; _mach_msg(6000002) ... __BKSHIDSetHardwareKeyboardLayout: ; _mach_msg(6000056) __BKSHIDGetHardwareKeyboardLanguage: ; _mach_msg(6000057) __BKSHIDSetEventRouters: ; _mach_msg(0) # OK, so it's not perfect -- I don't follow FP operations (yet)! __BKSHIDSetKeyCommands: ; _mach_msg(6000059) __BKSHIDSetStackshotCombos: ; _mach_msg(6000059) __BKSHIDSetTouchHand: ; _mach_msg(6000061) __BKSDisplayStart: ; _mach_msg(6001000) __BKSDisplayIsDisabled: ; _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:
- p - pointer
- s - char *
- c - char
- i - uint32_t
- @ - CFString
- x - hex
- B - block (^)
- l - uint64_t
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.
--sig
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
.
--ent
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">
<dict>
<key>allow-obliterate-device</key>
<true/>
# ....
<key>keychain-access-groups</key>
<array>
<string>apple</string>
<string>com.apple.preferences</string>
</array>
<key>vm-pressure-level</key>
<true/>
</dict>
</plist>
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 $? 0
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.

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:
- Full extraction of dylibs from cache
- improved code signing
Frequently Asked Questions
- Is this open source? No. Neither is my other reversing tool, Dextra (for Android). Free, yes. But not open source.
- *Why* is this not open source? Is Hopper? Is IDA? So I'm in good company :-)
jtool
started as a simpleotool -l
, but things spiraled out of control when I started doing the disassembly. There's just too much original code in there for me to just share with whatever license, that others can rip and not even mention credit in passing. Sorry. I speak from bitter experience here. It's still FREE though, which is more than what most tools (save, of course,otool
) can say. - Can I write plugins? better. You can script it with shell commands (it's designed to be
grep(1)
and other filter-friendly), and you can write entire programs to usejtool
's logic -machlib
anddisarm
- as dylibs. - What's up with color? Why is it disabled when I pipe, and messy when I JCOLOR=1 and pipe? Because color is done via curses. It's the default when you DON't pipe (i.e.
jtool
detects the stdout is a tty), and when you do it's disabled unless you insist (JCOLOR=1
orexport...
). But if you do that, useless -R
soless
(which ismore
) can handle the sequences. Or try--html
for colorized, hyperlinked HTML output you can save to a file. - What's the deal with ARM32/Thumb? Though the disassembler originally was for 32, when it became clear it's a choice between my sanity and support ARM32/Thumb, I opted for the former. This way I can write you other books and tools.
jtool
actually is pretty good still with ARM, but it's not officially supported. ARM64 is, though, and is fairly comprehensive (sans FP/SIMD, which only SpringBoard and a few other daemons use. I'll get to that at some point, if I get stranded for hours with absolutely no Internet and nothing else to do). Update: ARMv7k is kind of back - Why not use foundstone or some other disassembly framework? Because it wouldn't be Jtool then, now, would it? :-) The code is 100% original and mine, sharing no sources what-so-ever with any other code, open or closed (save for obvious include files)
- How do we contribute? By suggesting improvements, asking for features, or reporting bugs on the OS X Book Forum
- What can we do that will be COUNTER productive, and not in any way contribute?
You can bitch on Twitter whenever you encounter a crash:
and yes, you can use the pathetic otool which is rock solid, because it's as functional as a rock. And, you can NOT read the manual page which distinctly says that jtool does crash in some cases. I wish you could useotool
instead, but AAPL ruined what was good in it by rewriting it to useobjdump
, dumping it down, indeed.
Seriously, IF JTOOL CRASHES FOR YOU, LET ME KNOW AND I'LL FIX IT. COMPLAINING ACHIEVES NOTHING, AND THERE IS AN INSANE AMOUNT OF LOGIC IN JTOOL, SO YEAH, IT AIN'T PERFECT. But hey - it leaves otool in ashes, and it's FREE. I don't expect thanks, but just slamming the tool and not even reporting a bug is being a douche.