LLDB (Low-Level Debugger) is Apple’s debugger, built on the LLVM compiler infrastructure. It’s the standard debugger for iOS and macOS development, providing powerful capabilities for runtime inspection and manipulation.
LLDB remote debugging requires a jailbroken iOS device.
# SSH into your devicessh root@<device-ip># Install debugserver (usually comes with developer disk image)# Check if debugserver existsls /Developer/usr/bin/debugserver# If not present, copy from Xcode:# /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/# DeviceSupport/<iOS-version>/DeveloperDiskImage.dmg
2
Sign debugserver with Task-for-PID Entitlement
The stock debugserver lacks necessary entitlements. Create and sign:
# Find the app's process IDps aux | grep -i YourApp# Start debugserver attached to PIDdebugserver *:6666 --attach=<PID># Or attach by namedebugserver *:6666 --attach=YourApp
# Launch and wait for debuggerdebugserver *:6666 --waitfor=YourApp# Or launch specific binarydebugserver *:6666 /var/containers/Bundle/Application/<GUID>/YourApp.app/YourApp
*:6666 means listen on all interfaces on port 6666. You can use any port number.
4
Connect from Host Machine
Via WiFi
Via USB (Recommended)
# On your Maclldb# Connect to device(lldb) process connect connect://<device-ip>:6666# You should see:# Process <PID> stopped
# Forward port using iproxy (from libimobiledevice)# Install: brew install libimobiledeviceiproxy 6666 6666# In another terminallldb(lldb) process connect connect://localhost:6666
USB connection is faster and more stable than WiFi.
# Continue execution(lldb) continue(lldb) c# Step over (next line)(lldb) next(lldb) n# Step into (follow function calls)(lldb) step(lldb) s# Step out (finish current function)(lldb) finish# Step instruction (assembly level)(lldb) nexti(lldb) ni# Step into instruction(lldb) stepi(lldb) si
# Attach to process by PID(lldb) attach --pid <PID># Attach by name(lldb) attach --name YourApp# Detach from process(lldb) detach# Kill process(lldb) kill# Show process info(lldb) process status# List loaded modules(lldb) image list
# List all threads(lldb) thread list# Select thread(lldb) thread select <thread-id># Show backtrace (call stack)(lldb) bt(lldb) thread backtrace# Backtrace for all threads(lldb) bt all# Show current frame info(lldb) frame info# Select frame(lldb) frame select <frame-number>
# Break at address(lldb) br s -a 0x100004a20(lldb) breakpoint set --address 0x100004a20# Break at address with condition(lldb) br s -a 0x100004a20 -c 'x0 == 0'# Break at offset from module(lldb) br s -a YourApp[0x4a20]# List breakpoints(lldb) br list# Delete breakpoint(lldb) br delete <breakpoint-id># Disable/enable breakpoint(lldb) br disable <breakpoint-id>(lldb) br enable <breakpoint-id># Delete all breakpoints(lldb) br delete
# Show all registers(lldb) register read# Show specific register(lldb) register read x0(lldb) p/x $x0# Modify register(lldb) register write x0 0x1# Show floating point registers(lldb) register read --all# ARM64 common registers:# x0-x30: General purpose# x29: Frame pointer (FP)# x30: Link register (LR)# sp: Stack pointer# pc: Program counter# Function arguments are in:# x0: self (Objective-C)# x1: _cmd (selector)# x2-x7: Arguments 1-6
# Read memory at address(lldb) memory read 0x100004a20(lldb) x 0x100004a20# Read 64 bytes(lldb) memory read --count 64 0x100004a20(lldb) x/64xb 0x100004a20# Read as different formats:(lldb) x/s 0x100004a20 # String(lldb) x/4xw 0x100004a20 # 4 words (32-bit) in hex(lldb) x/4xg 0x100004a20 # 4 giant words (64-bit)(lldb) x/i 0x100004a20 # Instruction# Find memory(lldb) memory find -s "password" -- 0x100000000 0x200000000
# Dump stack(lldb) memory read $sp(lldb) x/32xg $sp# Show stack frame variables(lldb) frame variable(lldb) fr v# Show specific variable(lldb) frame variable username# Show with type info(lldb) frame variable -T username
# Search for function by name(lldb) image lookup -rn "validateCredentials"# Output shows address:# 1 match found in /var/containers/Bundle/Application/.../YourApp:# Address: YourApp[0x0000000100004a20]# Summary: YourApp`-[LoginViewController validateCredentials:password:]
2
Set Breakpoint
# Break at the function(lldb) br s -a 0x100004a20# Or by name(lldb) br s -n "-[LoginViewController validateCredentials:password:]"
3
Trigger and Inspect
# Continue execution(lldb) c# When breakpoint hits, inspect arguments(lldb) po $x0 # self(lldb) po $x1 # _cmd (selector)(lldb) po $x2 # first argument (username)(lldb) po $x3 # second argument (password)# Show registers(lldb) register read# Show backtrace(lldb) bt
# Find the jailbreak check function(lldb) image lookup -rn "isJailbroken"# Set breakpoint(lldb) br s -n "-[JailbreakDetector isJailbroken]"# When hit, force return false(lldb) thread return NO# Or modify return register before return(lldb) br s -a 0x<return-address>(lldb) br command add <breakpoint-id>> register write x0 0> c> DONE
# On devicedebugserver *:6666 --attach=YourApp# On Maclldb(lldb) process connect connect://<device-ip>:6666
2
Find Image Base
(lldb) image list YourApp# Output:# [ 0] <UUID> 0x0000000100000000 /var/containers/.../YourApp.app/YourApp# Note the load address: 0x0000000100000000
3
Dump Memory
# Get binary size(lldb) image dump sections YourApp# Dump to file (adjust size as needed)(lldb) memory read --outfile /tmp/decrypted.bin --binary \ 0x0000000100000000 0x0000000100000000+0x1000000
4
Fix Mach-O Header
# Transfer to Macscp root@<device-ip>:/tmp/decrypted.bin ~/# Use tools like MachOView or jtool to fix headers# Or use automated tools like Clutch or frida-ios-dump
# Break on any method containing "login"(lldb) br s -r ".*login.*" -s YourApp# Break on all methods in a file(lldb) br s -f LoginViewController.m# Break on Objective-C exception throw(lldb) br s -n objc_exception_throw# Break on malloc/free for tracking memory(lldb) br s -n malloc(lldb) br command add <breakpoint-id>> bt 5> register read x0> c> DONE
# Save breakpoints(lldb) br write -f ~/breakpoints.txt# Load breakpoints(lldb) br read -f ~/breakpoints.txt# Save commands to file(lldb) settings set target.save-jit-to-disk true
Use lldbinit for Automation
# Create ~/.lldbinitcommand script import ~/lldb_commands.py# Auto-connect shortcutcommand alias connect process connect connect://localhost:6666# Custom shortcutscommand alias bpt breakpoint set -acommand alias rd register read
Combine with Static Analysis
Use Ghidra/IDA to find interesting functions
Note addresses and function names
Set breakpoints in LLDB at those addresses
Examine runtime behavior
Return to static analysis with new insights
Performance Optimization
Use hardware breakpoints when possible (limited to 4 on ARM64)
Disable breakpoints when not needed
Use conditional breakpoints to reduce noise
Detach when done to avoid impact on app performance
# Check debugserver permissionsls -la /usr/bin/debugserver# Re-sign with proper entitlements# See "Sign debugserver" step above# Check if app has anti-debugging# Look for ptrace(PT_DENY_ATTACH)# Patch it out or use anti-anti-debugging tweaks
Symbols Not Loading
# Add symbol path(lldb) settings set target.debug-file-search-paths /path/to/symbols# Force symbol loading(lldb) target symbols add /path/to/YourApp.app/YourApp
Connection Drops
# Use USB instead of WiFiiproxy 6666 6666# Increase timeout(lldb) settings set plugin.process.gdb-remote.packet-timeout 60