disarm - Quick (& formerly dirty) CLI instruction lookup for ARM64,
turned full fledged binary analyzer

What was this?

A simple command line utility that takes as an argument a 32-bit hexadecimal number, and parses it as an ARM-64 instruction, providing the disassembly. I found myself using otool -j -tV | grep ... on random binaries too many times while looking for opcodes (especially in ARM64 injected code, like CydiaSubstrate's, see below), so I refactored my disassembler with a simple main() - and the result is presented herein. I'm making this available for OS X, iOS, Linux, and Android.


The download link is right here - Tar containing OS X/iOS (ARM64) Linux (ELF32/64) and Android - Updated 4/29/24 (yep, I'm still alive, people)

For the nightly build of disarm v2 (macOS x86_64/arm64) click here,
but don't forget xattr -d com.apple.quarantine ~/Downloads/disarm to get past annoying GateKeeper



Though this is a part of jtool[2]'s functionality (my OSX/iOS tool, which only works on Mach-O), disarm as a standalone tool can come in handy for anyone reversing ARM64 - on Android as well, and theoretically any ARM64-based OS. At some point I hope to integrate this into DexTRA so the latter can also disassemble ART's poorly generated native code.

What is this now? (Version 2.0 β)

Took me a long time, but my frustration with the poor maintenance of jtool2 by @Technologeeks and Darwin 23 breaking it (to the point of a segfault) made me realize it's easier to just refactor some Mach-O specific jtool2 code into a module inside disarm. And, while I was at it, why stop at Mach-O? I figured, let's 1up objdump by also supporting ELF (primarily, for my Android tribulations) and PE.

morpheus@eM1nent (~/Documents/OSXBook/2nd/src/disarm) % disarm

Usage: disarm 0x......  (for a quick hex->ARM64 lookup)
             or
       disarm [options] _file_

Where options are one or more of the following:
       -a  0x..[-0x..]: from[-to] this offfset (ELF-O/Mach-O)
       -A  0x..: Show offset for address (ELF-O/Mach-O)
       -b  0x..: rebase file to this virtual address (can also set BASE=0x...)
       -c:       color (can also setenv JCOLOR=1)
       -d[cdt]: dump, smart-dump as default, or specify:
             c: as C-Strings
             d: as data (just like hexdump -C)
             t: as text (i.e disassembly)
       -e: extract (offset range as 0x-..0x...) or region name
       -f  string|0x...: find string (or 0x...), reporting offset in binary
       -g [mnemonic,...]: find gadgets (specify opcodes, no arguments)
       -i: generic information about file
       -I: file format specific (ELF/Mach-O) information about file
       -l: list regions (Mach-O/ELF segments/sections)
       -L: file format specific (ELF/Mach-O only) listing
       -n[n]:    suppress NOP instructions (-nn: also DCD 0x0) (can also setenv NOPSUP=1 or 2)
       -o  0x..[-0x..]:  from[-to] this offset (must be in file bounds)
       -O  0x..: Show address for offset (ELF-O/Mach-O)
       -opcodes: also dump opcodes, not just disassembled instructions
       -P@0x...=....: patch at (@) offset 0x..., with bytes
       -q:       quick (don't follow register values)
       -r _region_: (smart-)dump _region_ or symbol (show regions using -l/-L, symbols using -S)
       -S: Show symbols in file (like nm(1), ELF/Mach-O only, for now)
       -v:       verbose

Mach-O Specific options:
       --signature:       Show code signature (just like jtool2 :-)

Environment variables:
        JCOLOR:  For color/curses output (not set)
        JDEBUG:  Internal debug output (left intentionally, for the curious - not set)
        NOPSUP:  NOP suppression: 1 to suppress NOPs, 2 to also suppress DCD 0x0 (not set)
        JA:      Force analysis (could be long, but recovers functions without LC_FUNCTION_STARTS, and enables matchers (not set)

This is J's disarm, 2 (beta2, with new ARM64 disassembly engine) - now handling ELF/mach-O/fat (and a bit of PE), too
Compiled on May 28 2023.
Latest version always at  http://NewOSXBook.com/tools/disarm.html

disarm can thus now achieve most of what jtool2 did, including:

STILL not supported:

Multiple binary format support

Being free of the chains to Mach-O, disarm now works the same was as jtool2 did on ELF and PE! It now introduces the notion of -i/-I and -l/-L for file-format agnostic (lowercase) or specific (uppercase) information:

morpheus@eM1nent (~/Documents/OSXBook/2nd/src/disarm) % disarm -i samples/init
samples/init File-format agnostic information:
File type:        shared library
Format:           ELF
Target OS:        Android 33.0
Architecture:     ARM64
File Size:        0x1fc338 (2081592) bytes
Linker:           /system/bin/bootstrap/linker64
Text region(s):   0xad000-0x1e9e68 (1298024/0x13ce68 bytes)
Data region(s):   0x1f0b28-0x1f0ce8 (448/0x1c0 bytes)
String region(s): 0x4f90-0xdc2c (35996/0x8c9c bytes)
Linker stubs:     0x1e9e70-0x1ebeb0 (8256/0x2040 bytes)
morpheus@eM1nent (~/Documents/OSXBook/2nd/src/disarm) % disarm -i samples/brctl
samples/brctl File-format agnostic information:
File type:        executable
Format:           Mach-O
Target OS:        iOS 16.0.0
Architecture:     ARM64e (ARMv8.3)
File Size:        0x388a0 (231584) bytes
Linker:           /usr/lib/dyld
Text region(s):   0x4fa8-0x17ec0 (@0x100004fa8-0x100017ec0) (77592/0x12f18 bytes)
         Entry:   0xed24
	 Data region(s):   0x28000-0x2c000 (16384/0x4000 bytes)
	 String region(s): 0x1f57e-0x23727 (16809/0x41a9 bytes)
	 Linker stubs:     0x17ec0-0x18a80 (3008/0xbc0 bytes)
	 BuildID/UUID:     97E2C3EA-34A8-3062-AEEE-5FD08FBAC54E
	 Binary is digitally signed (use --signature to view)
# -L: File format specific (like good ol' jtool2 -L 
morpheus@eM1nent (~/Documents/OSXBook/2nd/src/disarm) % ARCH=arm64e disarm -v -L /sbin/launchd | head -5
LC   0: LC_SEGMENT_64           Mem: 0x000000000-0x100000000    File: Not Mapped        ---/--- __PAGEZERO
LC   1: LC_SEGMENT_64           Mem: 0x100000000-0x10006c000    File: 0x0-0x6c000       r-x/r-x __TEXT
           Mem: 0x1000039a0-0x100052834 File: 0x000039a0-0x00052834      __TEXT.__text  (Normal)
           Mem: 0x100052834-0x100054434 File: 0x00052834-0x00054434      __TEXT.__auth_stubs    (Symbol Stubs)
           Mem: 0x100054434-0x100054438 File: 0x00054434-0x00054438      __TEXT.__init_offsets  (Initializer offsets)
# -l: File format agnostic (like  jtool2 --pages 
morpheus@eM1nent (~/Documents/OSXBook/2nd/src/disarm) % ARCH=arm64e disarm -l /sbin/launchd | head -5 
Regions:
0x0-0x6c000 -> 0x100000000-0x10006c000 __TEXT   (TEXT,READ-ONLY, 442368 bytes)
        0x39a0-0x52834 -> 0x1000039a0-0x100052834 __TEXT.__text (TEXT,READ-ONLY, 323220 bytes)
        0x52834-0x54434 -> 0x100052834-0x100054434 __TEXT.__auth_stubs  (TEXT,READ-ONLY, 7168 bytes)
        0x54434-0x54438 -> 0x100054434-0x100054438 __TEXT.__init_offsets        (TEXT,READ-ONLY, 4 bytes)
morpheus@eM1nent (~/Documents/OSXBook/2nd/src/disarm) % disarm -v -L samples/init | head -5
30 sections:
  (unnamed) (inactive) 0x0-0x0 (File: 0x0-0x0)
  .interp (program defined) 0x2e0-0x2ff (File: 0x2e0-0x2ff)
  .note.android.ident (note section) 0x300-0x318 (File: 0x300-0x318)
  .note.gnu.build-id (note section) 0x318-0x338 (File: 0x318-0x338)
morpheus@eM1nent (~/Documents/OSXBook/2nd/src/disarm) % disarm  -l samples/init | head -5
Regions:
0x2e0-0x2ff -> 0x2e0-0x2ff .interp      (31 bytes)
0x300-0x318 -> 0x300-0x318 .note.android.ident  (DATA, 24 bytes)
0x318-0x338 -> 0x318-0x338 .note.gnu.build-id   (32 bytes)
0x338-0x4538 -> 0x338-0x4538 .dynsym    (16896 bytes)

Sections/Segments/Symbols can now be dumped (as text or data) using -r (also for ELF/PE!) with same argument following:

morpheus@eM1nent (~/Documents/OSXBook/2nd/src/disarm) % disarm -r .text samples/init | grep \" | head -5
Showing .text (0xad000-0x1e9e68) as text
        strcmp(0,"ueventd",0,ARG3,ARG4);
        strcmp(0,"subcontext",0,ARG3,ARG4);
        strcmp(0,"selinux_setup",0,ARG3,ARG4);
        strcmp(0,"second_stage",0,ARG3,ARG4);
        __ZNSt3__124__put_character_sequenceIcNS_11char_traitsIcEEEERNS_13basic_ostreamIT_T0_EES7_PKS4_m(???,"Cannot find command: ",0x15,ARG3,ARG4);
	

Matchers

The main big thing about disarm is the addition of matchers which can be used with JA=1 to "analyze" the file in question. This is very much like jtool2's --analyze (which still kinda works..), but can now be used on any file (ELF, Mach-O, PE alike!), and - is entirely controlled by a filename.matchers you drop in the same directory as the target, or specify using JMATCHERS. This is pretty nifty:

#
# disarm's analysis module (formerly, of jtool2's joker module (a.k.a jokerlib)) pattern matcher file.
# 
# Syntax is intentionally kept simple:
#
# arg#|pattern|function_this_is_called_in|calling_function|Anything from this point is ignored
#
# where:
# 
#  - arg# = 0,1,2, or 3 only
#
#  - pattern = any partial but exact (from beginning) match. And, no, it can't have a '|' in it..
#  
#  - function_this_is_called_in = the containing function, NOT the calling function
#  calling_function (optional) = function whose argument this is
#
#  - The calling function = is optional, because many times you will have identified it already
#  since it gets called alot (e.g. OSKextLog, strcmp, panic etc).
#
#
#  # is a comment, obviously
#
#
#  This ensures that kernelcache analysis (a.k.a. jokerlib) will be future proof,
#  as AAPL won't really obfuscate those strings, and you can add/modify any pattern you want,
#  so... even if they do obfuscate, it won't help 'em :-). Also, it can now work on any binary!
# 
#  (And maybe they'll just realize (as they did with encrypted kernelcaches around iOS 9-10)
#   that the adversaries have symbolicated kernels, so to even the playing field, just leave symbols. 
#
# Usage is even simpler:  
# 
# JMATCHERS=/path/to/this/file disarm --analyze kernelcache...
#
# and then use generated companion file.
# 
# LICENSE:         
# 
#          FREE TO USE (AISE) **BUT** PLEASE GIVE CREDIT IF YOU FIND THIS USEFUL.
# 
#          IDA's "Lumina" and other "community databases" or some productz have long been fed by jtool2 - and that's 
#          perfectly fine, but please give credit where due - and spread the word of disarm, the newosxbook.com books 
#          and tools and get other people to use/read them, and hopefully find them useful.
#          Oh  - And please share your patterns, if they work for you, leaving this LICENSE comment intact.
#
# 
# Start from here
# ----------------
#
## Matches for argument 0:
#
0|iBoot version: %s\n|_PE_init_iokit|_printf,
0|dumpinfo does not fit into KDP packe|_create_panic_header|_kdp_printf
0|Software Step Debug exception from kern|_sleh_synchronous|_panic
0|Apparently on interrupt stack when taking user|_handle_user_abort|_panic_with_thread_kernel_state
0|ipc_kobject_label_check: att|_ipc_kobject_label_check|_panic
0|overflow on the ext manifest:|_load_trust_cache_with_type|_panic
0|kernel_mach_msg_rpc|_kernel_mach_msg_rpc|_panic
0|legacy trust caches are not supported|_load_legacy_trust_cache|_panic
0|ipc_kmsg_free: invalid kmsg (%p) head|_ipc_kmsg_free|_panic
0|KDPCoreStageInit|_kdp_core_init|lck_grp_alloc_init|
0|Invalidate HMAC function already set|_set_invalidate_hmac_function|_panic
0|vnode label timeout|_vnode_label|_vprint
0|corpse in flight count over-release|_task_crashinfo_release_ref|_panic
0|ipc_object_copyin_from_kernel: stra|_ipc_object_copyin_from_kernel|_panic
0|pmap_startup(): too many pages |_pmap_startup|_panic
0|vm_page_check_pageable_s|_vm_page_check_pageable_safe|_panic
0|vm_object_update: unexp|_vm_object_update|_panic
0|memory_object_synchronize_complet|_memory_object_synchronize_completed|_panic
#0|CODE SIGNING: process %d[%s]: rejecting invalid pa|_vm_fault_cs_handle_violation|_panic
0|zone_create: creating zone %s: flag %s|_zone_create_panic|_panic
0|zone_create: per-cpu zone has too much fragment|_zone_get_min_alloc_granule|_panic
0|zone_create: element size too large|_zone_create_ext|_panic
0|vm.permanent|_zone_init|_zone_create_ext
0|zone_element_encode doesn't work for z|_zone_create_ext|_panic
0|%s: Use KALLOC_TY|_zone_view_startup_init|_panic
0|zone_pva_t can't pack a |_zone_bootstrap|_panic
0|ksubmap[%s]: failed t|_zone_submap_init|_panic
0|%s: kernel_mach_msg_send (0x%x)|__ZN25IOServiceUserNotification7handlerEPvP9IOService|_IOLog
0|file_check_mmap increased|_mac_file_check_mmap|_panic
0|Using inactive port %p|__ipc_port_inactive_panic|_panic
0|ipc object %p is neither a port|_ipc_object_validate_preflight_panic|_panic
0|IORTC|_IOKitInitializeTime|__ZN9IOService16resourceMatchingEPKcP12OSDictionary
0|ipc_object_destroy:|_ipc_object_destroy|_panic
0|ipc_object_destroy_dest: stran|_ipc_object_destroy_dest|_panic
0|ipc_object_copyout_dest: stran|_ipc_object_copyout_dest|_panic
0|Trying to free an active port|_ipc_port_finalize|_panic
0|ipc_right_delta: st|_ipc_right_delta|_panic
0|ipc_right_terminate: st|_ipc_right_terminate|_panic
0|ipc_right_destroy: st|_ipc_right_destroy|_panic
0|ipc_right_copyin_check:|_ipc_right_copyin_check|_panic
0|ipc_right_copyout:|_ipc_right_copyout|_panic
0|mach_port_get_refs: st|_mach_port_get_refs|_panic
0|ikm_validate_sig:|_ikm_validate_sig|_panic
0|ipc_kmsg_body_sig:|_ikm_body_sig|_panic
0|size too large for the fast kmsg zo|_ipc_kmsg_alloc|_panic
0|ipc_mqueue_send|_ipc_mqueue_send_locked|_panic
0|ipc_mqueue_receive_results|_ipc_mqueue_receive_results|_panic
0|disabling imp_receiver on task with pend|_ipc_importance_task_mark_receiver|_panic
0|disabling de-nap on task with pending de-nap|_ipc_importance_task_mark_denap_receiver|_panic
0|ipc_entry_dealloc()|_ipc_entry_dealloc|_panic
0|NULL continuation passed to|_thread_handoff_parameter|_panic
0|Invalid attempt to wait while running the idle thread|_thread_mark_wait_locked|_panic
0|Gap in registered SFI class|_sfi_init|_panic
0|%s: unexpected machine timeout |_machine_timeout_with_suffix|_panic
0|thread_deallocate_daemon_init|_thread_deallocate_daemon_init|_panic
0|__kernel__|__ZN6OSKext10initializeEv|__ZN8OSSymbol11withCStringEPKc
0|com.apple.iokit.IOSurface|__ZN6OSKext10initializeEv|__ZN8OSSymbol17withCStringNoCopyEPKc
0|Bank ledger|_bank_init|_ledger_template_create|
0|%s>cpu %d failed to start|__low_level_processor_start|_panic
0|perfctl_class invalid @%s:%d|sched_perfcontrol_inherit_recommendation_from_tg|_panic
0|multiple entries with the same msgh_id|_mig_init|_panic
0|ipc_kobject_server: strange destination rights|_ipc_kobject_server|_panic
0|vm_compressor_init: Failed|_vm_compressor_init|_panic
0|vnode_pager_synchronize: memory_object_synchronize no longer support|_vnode_pager_synchronize|_panic
0|vnode_pager_init:|_vnode_pager_init|_panic
0|vnode_pager_setup:|_vnode_pager_setup|_panic
0|mach_msg_rpc_from_kernel|_mach_msg_rpc_from_kernel_body|_panic
0|Console I/O from interrupt-disabled context|_console_io_allowed|_panic
0|Invalid port passed|_fileport_notify|_panic
0|"Attempting to lookup/free|_vm_map_lookup_kalloc_entry_locked|_panic
0|kfree on an a|_kfree_addr|_panic
0|Ticket spinlock timeout; start|_tlock_contended|_panic
0|"OSMalloc_Tagre|_OSMalloc_Tagref|
0|"Lock bit not set %p = %lx|_lck_spin_assert|
0|"lck_mtx_unlock(): Attempt to r"|_lck_mtx_unlock_contended|
0|shared_region_find_key() no key for region '%s' @%s:%d|_shared_region_find_key|_panic
0|shared_region: sr_cache_header.imagesTextCount >= UINT32_MAX @%s:%d|_vm_shared_region_map_file__key_alloc|_panic
#0|"%s called before mach_bridge|_mach_bridge_recv_timestamps|_panic
0|"lck_mtx_convert_spin: Not held as spinlock|_lck_mtx_convert_spin|
0|"A kext releasing a(n)|__ZNK8OSSymbol13taggedReleaseEPKvi|
0|"A kext referenced an unresolved weak s|_kext_weak_symbol_referenced|
0|"%s timed out in phase|__ZN14IOPMrootDomain20panicWithShutdownLogEj|
0|IOEthernetController: failed to allocate timesync|__ZN20IOEthernetController4initEP12OSDictionary|
0|IOEthernetStatsKey|__ZN19IOEthernetInterfmory prepare failed|__ZN9IOSurface20flushProcessorCachesEv|
0|IOSurface::cleanProcessorCaches memory prepare failed|__ZN9IOSurface20cleanProcessorCachesEv|
0|sched_pri_decay_limit|_sched_init|_PE_parse_boot_argn|
0|bad regex index|_match_regex|_invalid||Sandbox
0|"no profile to evaluate"@|_eval|_panic|Sandbox
0|/device|_derive_vnode_mount_path||Sandbox
0|"released collection's refere|_profile_release|
0|"called on incorrect vno"|_getrdev0|no reserved PPL pages to relea|__pmap_release_reserved_ppl_page|_panic
0|PMAP_CS: attempted to lock a retired code directory entry from re|_pmap_cs_code_directory_from_region|_panic
0|PMAP_CS: attempted to lock a retired code directory entry: %p|_pmap_cs_lock_code_directory|_panic
0|%s: %#lx already locked |_pmap_ppl_lockdown_page_with_prot|_panic
0|PMAP_CS: attempted to get cod|_pmap_cs_code_directory_from_region|_printf
0|region to lock down not page alig|_pmap_ppl_lockdown_pages|_panic
0|%s: physical aperture or static region PTE is|_pmap_set_pte_xprr_perm|
0|attempted to disassociate non-existent TXM thread|__disassociates_TXM_thread|unknown|
0|segLOWESTAuxKC|_arm_vm_auxkc_init|_panic|
0|called exception_triage when it was forbidden by the boot environment|_exception_triage_thread|_panic
0|unexpected make-send count|__ipc_kobject_dealloc_bad_mscount_panic|_panic
0|port %p of type %d, expecting %d|__ipc_kobject_dealloc_bad_typt::taggedRetain|_panic
## Matches for argument 1:
1|wfe_events_sec|_wfe_timeout_configure|PE_parse_boot_argn
1|bpret|_arm_init|_PE_parse_boot_argn|
1|xnu-|_kdp_get_xnu_version|strnstr|
1|pmap_set_tpro_internal|_pmap_set_tpro_internal|_validate_pmap_mutable_internal
1|zet|_zone_tunables_fixup|_PE_parse_boot_argn
1|TrustCache|_load_static_trust_cache|_SecureDTGetProperty
1|Failed to seek to panic region fi|_dump_panic_buffer|_kern_coredump_log|
1|io_telemetry_limit|_task_init|_PE_parse_boot_argn_interernel_task|_process_name|_strlcpy # inline in 15..
1|kernel_task|_bsd_init|_strlcpy| # inline in 15..
1|atm_init|_kernel_bootstrap|_strncpy
1|sched_maintenance_thread|_sched_init_thread|thread_set_thread_name
1|gth 0x%x|_arm_vm_prot_init|_arm_vm_page_granular_prot
1|interrupt-controller|__ZN10AppleARMPE21platformAdjustServiceEP9IOService|__Z20IODTMatchNubWithKeysP15IORegistryEntryPKc|
1|root_device|_vfs_mountroot|_vfs_rootmountalloc_internal|
1|ctrr_cpu_start_lock|_ctrr_related|
1|routefs_lock|_routhread_init|
1|workq|_workq_init|
1|host_notify|_host_notify_init|
1|/private/var/mobile|_sb_ustate_manager_init|_sb_ustate_create|
1| (per-pid)|_populate_reporting_context|_sbuf_cat|
1|packbuff-container-manager-ipc|_sb_container_manager_request_new|_sb_packbuff_new_with_tag|
1|WakeOnMagicPacket|__ZN19IOEthernetInterface17controllerDidOpenEP19IONetworkController|__ZN14IOPMrootDomain14publishFeatureEPKcjPj|
#1|/..|_path_simplify|
1|kdp_cralizable|OSObject::serialize|_snprintf
2|kern_invalid mach trap|_kern_invalid|_Debugger
2|/chosen/machine-timeouts|_SecureDTGetProperty|machine_timeout_init_with_suffix
2|__kmod_start|_sdt_early_init|_getsectbynamefromheader
2|Importance for |_ipc_importance_extract_content|_scnprintf
2|Can't remove kext %s - not found.|__ZN6OSKext24removeKextWithIdentifierEPKcb|
2|Failed to query kext UUID (MA|__ZN6OSKext22copyKextUUIDForAddressEP8OSNumber|
2|Can't remove kext with load tag %d|__ZN6OSKext21removeKextWithLoadTagEjb|
2|Failed to record kext %s as a can|__ZN6OSKeng all eligible reg|__ZN6OSKext33sendAllKextPersonalitiesToCatalogEb|
2|cfil_acquire_sockbuf|_cfil_acquire_sockbuf|
2|Kext %s is not loadable during safe boo|__ZN6OSKext24copyAllKextPersonalitiesEb|
2|Can't release kext with load tag %d - no such kext is loaded.|_OSKextReleaseKextWithLoadTag|_OSKextLog|
2|idle #%d|_idle_thread_create|_snprintf|
2|Kext %s flushing dependenciesount_wait|_fsevent_unmount|_msleep|
2|forbidden-sandbox-reinit|_proc_apply_sandbox|__forbidden|
2|%s processor|_make_brand_string|_snprintf|
2|stackshot_in_flags|_kdp_stackshot_kcdata_format|_kcdata_add_uint64_with_description|
2|stackshot_in_pid|_kdp_stackshot_kcdata_format|_kcdata_add_uint32_with_description|
2|nfsrecoverrestart|nfs_recover|_tsleep|AAPL-REMOVE NFS CODE FROM iOS!!!
3|nwk_wq_thread_func|_nwk_wq_thread_func|_msleep|
3|nwk_wq_thread_cont|_nwk_wq_thread_cont|_msleep|
3|waitq sets|_ipc_init|_zinit|
3|%s system does not have monotonic clock|_clock_initialize_calendar|_os_log_internal|
3|process %s[%d] caught wa
3|memorystatus: purged %llu pages f|_memorystatus_kill_proc|_os_log_internal|
3|memorystatus_on_ledger_footprint_exceeded|_memorystatus_on_ledger_footprint_exceeded|_os_log_internal|
3|%lu.%03d memorystatus|_memorystatus_kill_process_sync|_os_log_internal|
3|EXC_RESOURCE ->|_memorystatus_log_exception|_os_log_internal|
3|%s: failed negative len|_m_pullup|_os_log_internal|
3|IOSurface count of %d approaching limit |__ZN13IOSurfaceRoot18add_surface_bufferEP9IOSurface|
3|vm objects|_vm_object_bootstrap|_zinit|
3|io_reprioritize_req|_vm_io_reprioritize_init|_zinitCH_IO|__os_log_internal
__DATA_CONST|val=0x0004000100000000|syscall_table

Examples

For example, consider manually crafted code, e.g CydiaSubstrate:

Zephyr:JTool morpheus$ jtool -d 29c4 CydiaSubstrate
	29c4    MOVZ   W8, #43455, LSL #16; ; ->R8 = 0xa9bf0000 
        29c8    MOVK   X8, #8166; ; R8 += 1fe6 =.. 0xa9bf1fe6 
        29cc    STR    W8, [X19, #0]; ; *((X19) + 0x0) = X8 0xa9bf1fe6
        29d0    MOVZ   W8, #43455, LSL #16; ; ->R8 = 0xa9bf0000 
        29d4    MOVK   X8, #6116; ; R8 += 17e4 =.. 0xa9bf17e4 
        29d8    STR    W8, [X19, #4]; ; *((X19) + 0x4) = X8 0xa9bf17e4
        29dc    MOVZ   W8, #43455, LSL #16; ; ->R8 = 0xa9bf0000 
        29e0    MOVK   X8, #4066; ; R8 += fe2 =.. 0xa9bf0fe2 
        29e4    STR    W8, [X19, #8]; ; *((X19) + 0x8) = X8 0xa9bf0fe2
        29e8    MOVZ   W8, #43455, LSL #16; ; ->R8 = 0xa9bf0000 
        29ec    MOVK   X8, #2016; ; R8 += 7e0 =.. 0xa9bf07e0 
        29f0    STR    W8, [X19, #12]; ; *((X19) + 0xc) = X8 0xa9bf07e0

As you can see, jtool can figure out the values, but can't actually "understand" that's injected code, so it won't disassemble those further.
With disarm, you could decipher the instructions like so:

Zephyr:JTool morpheus$ ./disarm 0xa9bf1fe6
0xa9bf1fe6	STP X6, X7, [SP,#-16]!
Zephyr:JTool morpheus$ ./disarm 0xa9bf17e4
0xa9bf17e4	STP X4, X5, [SP,#-16]!
Zephyr:JTool morpheus$ ./disarm 0xa9bf0fe2
0xa9bf0fe2	STP X2, X3, [SP,#-16]!
Zephyr:JTool morpheus$ ./disarm 0xa9bf07e0
0xa9bf07e0	STP X0, X1, [SP,#-16]!
# you get the idea...

v0.2: File disassembly

Added an option to dump arbitrary files and attempt to disassemble. This is super useful for dumped bootloaders (e.g. iBoot, or Samsung S6, as shown here):

morpheus@Zephyr(~)$~/Documents/Work/JTool/disarm sboot.bin  | more
...
# Try to dump file - when the instructions make sense,
# you know it has to be code
0x00000000      0x00000010      DCD 0x10  # Offset of code
0x00000004      0xe99c208a      DCD 0xe99c208a 
0x00000008      0x00000000      DCD 0x0 
0x0000000c      0x00000000      DCD 0x0 
0x00000010      0x14000002      B 0x18   # branch to main
        -------------------------------
0x00000014      0x14000000      B 0x14   halt 
        -------------------------------
0x00000018      0x58000a80      LDR X0, #336            ; 0x168 
0x0000001c      0xb9400000      LDR W0, [X0, #0] 
0x00000020      0xd2b00001      MOVZ X1, 0x8000, LSL #16 
0x00000024      0x6a01001f      ANDS W31, W0, W1 
0x00000028      0x54000740      B.EQ 0x110 
0x0000002c      0x58000a20      LDR X0, #324            ; 0x170 
0x00000030      0xb9400000      LDR W0, [X0, #0] 
0x00000034      0x7200001f      TST W0, #1 
0x00000038      0x540006c0      B.EQ 0x110
0x0000003c      0x580009e0      LDR X0, #79
0x00000040      0xb9400000      LDR W0, [X0, #0]
0x00000044      0x12000400      AND W0, W0, #0x3 
0x00000048      0x71000c1f      CMP W0, #3
0x0000004c      0x540000c0      B.EQ 0x64
..
0x00000060      0x1400002c      B 0x110
       ---------------------------------
0x00000064      0x58000920      LDR X0, #73
0x00000068      0xb9400000      LDR W0, [X0, #0]
0x0000006c      0x12000400      AND W0, W0, #0x3 

Also added an Android Binary. ARMv7 binary, only because it was quicker to compile - will also work on ARM64 devices, of course.

Note, that there's a greater chance of unrecognized instructions in bootloaders (ISB, DSB, etc). If you find any (DCDs...) just let me know, and I'll add them in

v0.3: Register following

Also added most ARM64 ELx registers. Whew

v0.4: Auto-String

disarming a file will automatically figure out printable string arguments on calls (i.e. BL instructions), and print them out. This is really useful for finding panic, printf and the like in pretty much any binary of any OS - boot loader, kernel, or arbitrary user mode - provided it is ARM64 (for now). All you need to do is grep quotes ("). A few examples:

Zephyr:JTool morpheus$ disarm ~/Documents/Android/Devices/6E/sboot.bin | grep \" 
..
0x0003fa54	0x9400147f	BL 0x44c50 	; = 0x44c50("Kernel Image..")
0x0003fa6c	0x94001479	BL 0x44c50 	; = 0x44c50("- %s..")
0x0003facc	0x94001461	BL 0x44c50 	; = 0x44c50(" This is a non-secure chip. Skip...")
0x0003fb04	0x94001453	BL 0x44c50 	; = 0x44c50(" BL1 from SD CARD..")
0x0003fb38	0x94001446	BL 0x44c50 	; = 0x44c50("%s: failed.(%d)..")
0x0003fbd4	0x9400141f	BL 0x44c50 	; = 0x44c50("%s: passed.(%d)..","ace_hash_sha_digest"..))
0x0003fc18	0x9400140e	BL 0x44c50 	; = 0x44c50("%s: failed.(%d)..","ace_hash_sha_digest"..))
0x0003fc6c	0x940013f9	BL 0x44c50 	; = 0x44c50(".. SHA_INIT: 0x%08x 0x%08x..")
0x0003fc88	0x940013f2	BL 0x44c50 	; = 0x44c50(" sizeof  HASH_INFO:%d, ace_hash_ctx_t: %d, image_info: %d, shared_sigdata_t:%d....")
0x0003fcd8	0x940013de	BL 0x44c50 	; = 0x44c50("%s: failed.(%d)..","ace_hash_sha_init"..))
0x0003fd1c	0x940013cd	BL 0x44c50 	; = 0x44c50(" SHA_UPDATE-S: 0x%08x@0x%08x..")
0x0003fd68	0x940013ba	BL 0x44c50 	; = 0x44c50("%s: failed.(%d)..","ace_hash_sha_update"..))
0x0003fdac	0x940013a9	BL 0x44c50 	; = 0x44c50(" SHA_FINAL: 0x%08x 0x%08x %d..")
0x0003fe10	0x94001390	BL 0x44c50 	; = 0x44c50("%s: failed.(%d)..","ace_hash_sha_final"..))
0x00040260	0x9400127c	BL 0x44c50 	; = 0x44c50("reboot reason: ")
0x00040308	0x94001252	BL 0x44c50 	; = 0x44c50("Core stat at previous(IRAM)..")
0x000403a4	0x9400122b	BL 0x44c50 	; = 0x44c50("Core stat at previous(KERNEL)..")
..
Zephyr:JTool morpheus$ disarm ~/Documents/iOS/JB/TaiG8.3/kernel.8.4.iPhone6.dump | grep \" 
0x000025a0      0x9403736b      BL 0xdf34c      ; = 0xdf34c(?,"/var/vm/swapfile"..)) # strncpy
0x00002c38      0x94008ca9      BL 0x25edc      ; = 0x25edc(?,"default_pager"..))    # lck_grp_init
0x00002de4      0x940075a6      BL 0x2047c      ; = 0x2047c(""%s[KERNEL]: %s"")      # panic!
0x00002e44      0x9400758e      BL 0x2047c      ; = 0x2047c(""can't start backing store monitor thread"")

# And on iBoot (8.x) Wicked useful, qwupz :-)
bash-3.2# disarm /Users/morpheus/Documents/iOS/JB/iBoot/iBootDump-iPod7,1-8.4  | grep \" 
0x000004e4	0x9400346e	BL 0xd69c 	; = 0xd69c("asp_fw")
0x00000500	0x94003467	BL 0xd69c 	; = 0xd69c("nand_syscfg")
0x00000510	0x94005a85	BL 0x16f24 	; = 0x16f24("nand_syscfg")
0x00000604	0x94008ecd	BL 0x24138 	; = 0x24138("iBoot not saving panic log...")
0x0000064c	0x94008ebb	BL 0x24138 	; = 0x24138("Panic saved, full reset...")
...
# ... Oh, and - AAPL - those hashes instead of messages in 9.x will only get you this far. 
bash-3.2# 

v0.8: Gadget locator

Disarm can now look for gadgets - just specify -g with a string of comma separated mnemonics, and disarm will find them for you. This is super useful with jtool2. For example, the following gets you _realhost on iOS kernels:

Chimera:disarm morpheus$ disarm -g ADRP,ADD,RET /tmp/kernel  | head -3
0xa288d4: ADRP X0, 5834
0xa288d4: ADD X0, X0, #1912
0xa288d4: RET 
Chimera:disarm morpheus$ jtool2  -o 0xa288d4 /tmp/kernel
Offset 0xa288d4 is in __TEXT_EXEC.__text, loaded at address 0xfffffff007a2c8d4

As usual, I don't filter, but let grep(1) do it for me. For example, to find ADD X0, X0, ... gadgets:

Chimera:disarm morpheus$ disarm -g ADD,RET /tmp/kernel  | grep "X0, X0, #16$"
0x137cb6c: ADD X0, X0, #16
0x1509ccc: ADD X0, X0, #16
0x1764ca8: ADD X0, X0, #16
0x1ebc0c0: ADD X0, X0, #16
Chimera:disarm morpheus$ jtool2  -d 0xfffffff008380b6c,5 /tmp/kernel
Disassembling 5 bytes from address 0xfffffff008380b6c (offset 0x137cb6c):
_func_fffffff008380b6c:
fffffff008380b6c        0x91004000  ADD         X0, X0, #16              ; R0 = R0 + 0x10 = 0x10
fffffff008380b70        0xd65f03c0  RET                                  ; 
        return(?);

v0.9: Built-in HexDump, uint32_t magic detection, find in file and MOVZKKK

Limitations

Plenty. I don't support all the ARM64 instruction set. I admit - I'm weak. I couldn't read through the entire 5,000 pages of the ARMv8 Reference document. To see what I do support, run the tool with "opcodes".

This is not meant to be a complete tool, and you may end up just getting your input echoed back at you as a DCD 0x..... If you think those instructions are useful, and want to help me improve this tool, just drop me a note - preferably through The book forum.

Q&A

Changelog