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:
Transparently work on FAT files by using ARCH=
display signatures (--signature
), which (unlike jtool2
, which crashes on new 0xfade8181 launch constraints) is also future proof (annoying array underflow on negative slots fixed..)
Auto fixup of DYLD_CHAINED_FIXUPS. If you want to see the "behind the scenes", use JFIXUP=2
. To disable it (really, bad idea, but nice to see what it looks like otherwise) use JFIXUP=NO
.
jtool2 -F
is now disarm -f
(to find in memory), and can also find 0x....
Dump MIG tables (actually better than jtool2
, since MIG system 0 (technically invalid, but used by diskarbitrationd
and friends) is also detected.
Extract sections/segments (-e
)
Transparently work through .img4
and bvx2
payloads
Kernelcache:
Mach trap table detection
Syscall table detection
JCOLOR=1
:-)
STILL not supported:
-g
for gadgets
--sign
for fake signing
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
disarm
ing 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
disarm -d
now does exactly what hexdump -C
does
New -f
switch will find a string in a file, display all instances with offsets
MOVZ followed by MOVK.. will now show up as one MOVZ[K[K[K]]] pseudo-instruction. I had that in jtool v1 forever, and now it's also in jtool2
Magic constants which happen to be ASCII readable are now displayed when MOV[Z[K*] are followed
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
Any plans to support ARM32/Thumb? - I initially sank myself into the quagmire of disassembly with ARMv7, and jtool
does perform (very) rudimentary disassembly on ARM32/thumb. To be honest, however, I gave up on that assembly. Simply too many cases to cover, and I don't know what the ARM-folk were injesting/injecting/imbibing/smoking when they figured out those bitmasks.. Seriously, ARM32/Thumb is genius/sick. If you really want support, let me know and I'll provide what I have.
What are the offsets I see? - Raw file offsets. You can use -base
to emulate a virtual memory address. Otherwise these are just in the file itself. Because ARM64 code is relocatable, that's all the CPU - and the reverser - usually need to know.
Why not use Capstone/IDA Python/libopcodes/<fill in your favorite open source disassembly engine here > - Because. I write my own tools, with my own code, period. In the case of disassembly, the only way to really gain proficiency is to grind bits, till you get to know every instruction. Please don't email me suggestions about using IDA/Capstone/Radare/etc. If you find bugs, help me improve the tool and report them!
Any plans to open source disarm and/or jtool? - Does the world need yet another GitHub repository and a disassembler? Besides, my tools remain free.
Are you still on track for MOXiI 2? - Volume III is out.
Changelog
04/23/2019 - v0.9
02/13/2019 - v0.8 Back with a vengeance.. and gadget locating
06/12/2016 - v0.4 stable
04/04/2016 - added Android binary (disarm.armv7.ELF), and file disarming
08/17/2015 - added download link ...
08/16/2015 - v0.1 - Initial