├── README.md ├── bk_addr.py ├── bk_objc.py ├── block.py └── idevice_connect.py /README.md: -------------------------------------------------------------------------------- 1 | ### A Python script to disassemble an Objective-C block in lldb 2 | ================= 3 | 4 | A Python script for `lldb` that prints an Objective-C block signature and disassemble its invoke function. 5 | 6 | In order to use the script in an embedded python interpreter using lldb you can import it by running the command 7 | 8 | ``` 9 | command script import /path/to/block.py` 10 | ``` 11 | 12 | Alternatively, you can add `command script import /path/to/block.py` to your `~/.lldbinit`. 13 | 14 | Usage: 15 | 16 | block_disass variable 17 | 18 | The following options are available: 19 | 20 | -d, --disass 21 | Disassembles the invoke function of the block. If no option is specified, 22 | --disass is assumed. 23 | 24 | -n, --number-instructions 25 | The number of instructions in the invoke function to disassemble. 26 | 27 | -s, --signature 28 | Prints the block signature, formatted with NSMethodSignature. 29 | 30 | Note that --number-instructions is only taken into account when used with --disass. 31 | -------------------------------------------------------------------------------- /bk_addr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | ''' 4 | Author: 5 | Proteas 6 | Date: 7 | 2015-04-17 8 | Purpose: 9 | set breakpoint on address, auto calcute slide 10 | Usage: 11 | add the following line to ~/.lldbinit 12 | command script import ~/.lldb/bk_addr.py 13 | ''' 14 | 15 | import lldb 16 | import commands 17 | import shlex 18 | import optparse 19 | import re 20 | 21 | def __lldb_init_module (debugger, dict): 22 | debugger.HandleCommand('command script add -f bk_addr.bk_addr bk_addr') 23 | print 'The "bk_addr" command has been installed, usage: bk_addr 0x00000001' 24 | 25 | def create_command_arguments(command): 26 | return shlex.split(command) 27 | 28 | def bk_addr(debugger, command, result, dict): 29 | 30 | # common objects 31 | target = debugger.GetSelectedTarget() 32 | process = target.GetProcess() 33 | thread = process.GetSelectedThread() 34 | frame = thread.GetSelectedFrame() 35 | 36 | # parse command params 37 | args = create_command_arguments(command) 38 | if len(args) != 1: 39 | print "[bk_addr]: invalid param" 40 | return 41 | 42 | # find text section 43 | target_module = target.GetModuleAtIndex(0) 44 | text_section = None 45 | for sec in target_module.section_iter(): 46 | if sec.GetName() == '__TEXT': 47 | text_section = sec 48 | 49 | if not text_section: 50 | print "bk_addr]: can't find __TEXT" 51 | return 52 | 53 | # calcute slide 54 | slide = text_section.GetLoadAddress(target) - text_section.GetFileAddress() 55 | print "[bk_addr]: module slide is: %#x" % slide 56 | 57 | # get file address 58 | target_file_address = long(args[0], 0) 59 | # calcute load address 60 | target_load_address = target_file_address + slide 61 | 62 | # set breakpoint on address 63 | target.BreakpointCreateByAddress(target_load_address) 64 | -------------------------------------------------------------------------------- /bk_objc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | ''' 4 | Author: 5 | Proteas 6 | Date: 7 | 2014-03-05 8 | Purpose: 9 | set breakpoint without symbols, for examle: stripped macho 10 | Usage: 11 | add the following line to ~/.lldbinit 12 | command script import ~/.lldb/bt_objc.py 13 | ''' 14 | 15 | import lldb 16 | import commands 17 | import shlex 18 | import optparse 19 | import re 20 | 21 | def __lldb_init_module (debugger, dict): 22 | debugger.HandleCommand('command script add -f bk_objc.bt_objc bk_objc') 23 | print 'The "bk_objc" command has been installed' 24 | 25 | def create_command_arguments(command): 26 | return shlex.split(command) 27 | 28 | def is_command_valid(args): 29 | "" 30 | if len(args) == 0: 31 | return False 32 | 33 | arg = args[0] 34 | if len(arg) == 0: 35 | return False 36 | 37 | ret = re.match('^[+-]\[.+ .+\]$', arg) # TODO: more strict 38 | if not ret: 39 | return False 40 | 41 | return True 42 | 43 | def get_class_name(arg): 44 | match = re.search('(?<=\[)[^\[].*[^ ](?= +)', arg) # TODO: more strict 45 | if match: 46 | return match.group(0) 47 | else: 48 | return None 49 | 50 | def get_method_name(arg): 51 | match = re.search('(?<= )[^ ].*[^\]](?=\]+)', arg) # TODO: more strict 52 | if match: 53 | return match.group(0) 54 | else: 55 | return None 56 | 57 | def is_class_method(arg): 58 | if len(arg) == 0: 59 | return False 60 | 61 | if arg[0] == '+': 62 | return True 63 | else: 64 | return False 65 | 66 | def get_selected_frame(): 67 | debugger = lldb.debugger 68 | target = debugger.GetSelectedTarget() 69 | process = target.GetProcess() 70 | thread = process.GetSelectedThread() 71 | frame = thread.GetSelectedFrame() 72 | 73 | return frame 74 | 75 | def get_class_method_address(class_name, method_name): 76 | frame = get_selected_frame(); 77 | class_addr = frame.EvaluateExpression("(Class)object_getClass((Class)NSClassFromString(@\"%s\"))" % class_name).GetValueAsUnsigned() 78 | if class_addr == 0: 79 | return 0 80 | 81 | sel_addr = frame.EvaluateExpression("(SEL)NSSelectorFromString(@\"%s\")" % method_name).GetValueAsUnsigned() 82 | has_method = frame.EvaluateExpression("(BOOL)class_respondsToSelector(%d, %d)" % (class_addr, sel_addr)).GetValueAsUnsigned() 83 | if not has_method: 84 | return 0 85 | 86 | method_addr = frame.EvaluateExpression('(void *)class_getMethodImplementation(%d, %d)' % (class_addr, sel_addr)) 87 | 88 | return method_addr.GetValueAsUnsigned() 89 | 90 | def get_instance_method_address(class_name, method_name): 91 | frame = get_selected_frame(); 92 | class_addr = frame.EvaluateExpression("(Class)NSClassFromString(@\"%s\")" % class_name).GetValueAsUnsigned() 93 | if class_addr == 0: 94 | return 0 95 | 96 | sel_addr = frame.EvaluateExpression("(SEL)NSSelectorFromString(@\"%s\")" % method_name).GetValueAsUnsigned() 97 | has_method = frame.EvaluateExpression("(BOOL)class_respondsToSelector(%d, %d)" % (class_addr, sel_addr)).GetValueAsUnsigned() 98 | if not has_method: 99 | return 0 100 | 101 | method_addr = frame.EvaluateExpression('(void *)class_getMethodImplementation(%d, %d)' % (class_addr, sel_addr)) 102 | 103 | return method_addr.GetValueAsUnsigned() 104 | 105 | def bt_objc(debugger, command, result, dict): 106 | args = create_command_arguments(command) 107 | 108 | if not is_command_valid(args): 109 | print 'please specify the param, for example: "-[UIView initWithFrame:]"' 110 | return 111 | 112 | arg = args[0] 113 | class_name = get_class_name(arg) 114 | method_name = get_method_name(arg) 115 | 116 | address = 0 117 | if is_class_method(arg): 118 | address = get_class_method_address(class_name, method_name) 119 | else: 120 | address = get_instance_method_address(class_name, method_name) 121 | 122 | if address: 123 | lldb.debugger.HandleCommand ('breakpoint set --address %x' % address) 124 | else: 125 | print "fail, please check the arguments" 126 | -------------------------------------------------------------------------------- /block.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | ''' 4 | Add this to ~/.lldbinit 5 | command script import ~/.lldb/block.py 6 | ''' 7 | 8 | import lldb 9 | import commands 10 | import shlex 11 | import optparse 12 | 13 | def __lldb_init_module (debugger, dict): 14 | debugger.HandleCommand('command script add -f block.block_disass_command block_disass') 15 | print 'The "block_disass" command has been installed' 16 | 17 | def create_command_arguments(command): 18 | return shlex.split(command) 19 | 20 | def create_block_disass_parser(): 21 | usage = "usage: %prog arg1 [--disass -d] [--number-instructions -n] [--signature -s]" 22 | parser = optparse.OptionParser(prog='block_disass', usage=usage) 23 | parser.add_option('-d', '--disass', action='store_true', dest='disass', default=False) 24 | parser.add_option('-n', '--number-instructions', dest='numberinstructions', default=20) 25 | parser.add_option('-s', '--signature', action='store_true', dest='signature', default=False) 26 | return parser 27 | 28 | def block_disass_command(debugger, command, result, dict): 29 | cmd_args = create_command_arguments(command) 30 | parser = create_block_disass_parser() 31 | 32 | try: 33 | (options, args) = parser.parse_args(cmd_args) 34 | except: 35 | return 36 | 37 | if len(args) == 0: 38 | print "You need to specify the name of a variable or an address" 39 | return 40 | 41 | variable_arg = args[0] 42 | number_instructions = options.numberinstructions 43 | should_signature = options.signature 44 | should_disass = options.disass or (not options.signature and not options.disass) 45 | 46 | target = debugger.GetSelectedTarget() 47 | process = target.GetProcess() 48 | thread = process.GetSelectedThread() 49 | frame = thread.GetSelectedFrame() 50 | 51 | variable = frame.FindVariable(variable_arg) 52 | if variable.IsValid(): 53 | address = variable.GetValueAsSigned() 54 | else: 55 | try: 56 | address = int(variable_arg, 0) 57 | except: 58 | print "The argument is not a valid address or variable in the frame" 59 | return 60 | 61 | if should_signature: 62 | print_block_signature(debugger, target, process, address) 63 | if should_disass: 64 | disass_block_invoke_function(debugger, target, process, address, number_instructions) 65 | 66 | ''' 67 | struct Block_literal_1 { 68 | void *isa; 69 | int flags; 70 | int reserved; 71 | void (*invoke)(void *, ...); 72 | struct Block_descriptor_1 { 73 | unsigned long int reserved; 74 | unsigned long int size; 75 | void (*copy_helper)(void *dst, void *src); 76 | void (*dispose_helper)(void *src); 77 | const char *signature; 78 | } *descriptor; 79 | }; 80 | ''' 81 | 82 | def print_block_signature(debugger, target, process, block_address): 83 | pointer_size = 8 if arch_for_target_is_64bit(target) else 4 84 | 85 | flags_address = block_address + pointer_size # The `flags` integer is after a pointer in the struct 86 | 87 | flags_error = lldb.SBError() 88 | flags = process.ReadUnsignedFromMemory(flags_address, 4, flags_error) 89 | if not flags_error.Success(): 90 | print "Could not retrieve the block flags" 91 | return 92 | 93 | block_has_signature = ((flags & (1 << 30)) != 0) # BLOCK_HAS_SIGNATURE = (1 << 30) 94 | block_has_copy_dispose_helpers = ((flags & (1 << 25)) != 0) # BLOCK_HAS_COPY_DISPOSE = (1 << 25) 95 | 96 | if not block_has_signature: 97 | print "The block does not have a signature" 98 | return 99 | 100 | block_descriptor_address = block_address + 2 * 4 + 2 * pointer_size # The block descriptor struct pointer is after 2 pointers and 2 int in the struct 101 | 102 | block_descriptor_error = lldb.SBError() 103 | block_descriptor = process.ReadPointerFromMemory(block_descriptor_address, block_descriptor_error) 104 | if not block_descriptor_error.Success(): 105 | print "Could not read the block descriptor struct" 106 | return 107 | 108 | signature_address = block_descriptor + 2 * pointer_size # The signature is after 2 unsigned int in the descriptor struct 109 | if block_has_copy_dispose_helpers: 110 | signature_address += 2 * pointer_size # If there are a copy and dispose function pointers the signature 111 | 112 | signature_pointer_error = lldb.SBError() 113 | signature_pointer = process.ReadPointerFromMemory(signature_address, signature_pointer_error) 114 | 115 | signature_error = lldb.SBError() 116 | signature = process.ReadCStringFromMemory(signature_pointer, 256, signature_error) 117 | if not signature_error.Success(): 118 | print "Could not retrieve the signature" 119 | return 120 | 121 | escaped_signature = signature.replace('"', '\\"') 122 | 123 | method_signature_cmd = 'po [NSMethodSignature signatureWithObjCTypes:"' + escaped_signature + '"]' 124 | debugger.HandleCommand(method_signature_cmd) 125 | 126 | def disass_block_invoke_function(debugger, target, process, block_address, instruction_count): 127 | pointer_size = 8 if arch_for_target_is_64bit(target) else 4 128 | 129 | invoke_function_address = block_address + pointer_size + 2 * 4 # The `invoke` function is after one pointer and 2 int in the struct 130 | 131 | invoke_function_error = lldb.SBError() 132 | invoke_function_pointer = process.ReadPointerFromMemory(invoke_function_address, invoke_function_error) 133 | if not invoke_function_error.Success(): 134 | print "Could not retrieve the block invoke function pointer" 135 | return 136 | 137 | disass_cmd = "disassemble --start-address " + str(invoke_function_pointer) + " -c " + str(instruction_count) 138 | debugger.HandleCommand(disass_cmd) 139 | 140 | arch_64 = ['armv64', 'x86_64'] 141 | arch_32 = ['i386', 'armv7', 'armv7s'] 142 | 143 | def arch_for_target_is_64bit(target): 144 | arch = target.GetTriple().split('-')[0] 145 | return arch in arch_64 146 | -------------------------------------------------------------------------------- /idevice_connect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | ''' 4 | Author: 5 | Proteas 6 | Date: 7 | 2014-08-20 8 | Purpose: 9 | connect idevice 10 | Usage: 11 | add the following line to ~/.lldbinit 12 | command script import ~/.lldb/idevice_connect.py 13 | ''' 14 | 15 | import lldb 16 | import commands 17 | import shlex 18 | import optparse 19 | import re 20 | 21 | def __lldb_init_module (debugger, dict): 22 | debugger.HandleCommand('command script add -f idevice_connect.idevice_connect idevice_connect') 23 | print 'The "idevice_connect" command has been installed' 24 | 25 | def idevice_connect(debugger, command, result, dict): 26 | lldb.debugger.HandleCommand ('platform select remote-ios') 27 | lldb.debugger.HandleCommand ('process connect connect://127.0.0.1:11022') --------------------------------------------------------------------------------