Chapters

Hide chapters

Advanced Apple Debugging & Reverse Engineering

Third Edition · iOS 12 · Swift 4.2 · Xcode 10

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section III: Low Level

Section 3: 7 chapters
Show chapters Hide chapters

Section IV: Custom LLDB Commands

Section 4: 8 chapters
Show chapters Hide chapters

24. Script Bridging with Options & Arguments
Written by Derek Selander

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

When you’re creating a custom debugging command, you’ll often want to slightly tweak functionality based upon options or arguments supplied to your command. A custom LLDB command that can do a job only one way is a boring one-trick pony.

In this chapter, you’ll explore how to pass optional parameters (a.k.a. options) as well as arguments (parameters which are expected) to your custom command to alter functionality or logic in your custom LLDB scripts.

You’ll continue working with the bar (“break-after-regex”) command you created in the previous chapter. In this chapter, you’ll finish up the bar command by adding logic to handle options in your script.

By the end of this chapter, the bar command will have logic to handle the following optional parameters:

  • Non-regular expression search: Using the -n or --non_regex option will result in the bar command using a non-regular expression breakpoint search instead. This option will not take any additional parameters.
  • Filter by module: Using the -m or --module option will only search for breakpoints in that particular module. This option will expect an additional parameter which specifies the name of the module.
  • Stop on condition: Using the -c or --condition option, the bar command will evaluate the given condition after stepping out of the current function. If True, execution will stop. If False, execution will continue. This option will expect an additional parameter which is a string of code that will be executed and evaluated as an Objective-C BOOL.

This will be a dense but fun chapter. Make sure you’ve got a good supply of caffeine!

Setting up

If you’ve gone through the previous chapter and your bar command is working, then you can continue using that script and ignore this part. Otherwise, head on over to the starter folder in this chapter’s resources, and copy the BreakAfterRegex.py file into your ~/lldb folder. Make sure your ~/.lldbinit file has the following line which you should have from the previous chapter:

command script import ~/lldb/BreakAfterRegex.py

If you’ve any doubts if this command loaded successfully into LLDB, simply fire up a new LLDB instance in Terminal:

lldb

Then check for the help docstring of the bar command:

(lldb) help bar

If you get an error, it’s not successfully loaded; but if you got the docstring, you’re golden.

The RWDevCon project

For this chapter, you’ll use an app called RWDevcon. It’s a live app, available in the App Store (https://itunes.apple.com/us/app/rwdevcon-the-tutorial-conference/id958625272).

The optparse Python module

The lovely thing about LLDB Python scripts is you have all the power of Python — and its modules — at your disposal.

some_command woot -b 34 -a "hello world"

Adding options without params

With the knowledge you need to educate your parser with what arguments are expected, it’s time to add your first option which will alter the functionality of the bar command to apply the SBBreakpoint without using a regular expression, but instead use a normal expression.

some_command -f
some_command -f1
import optparse
import shlex
import shlex
command = '"hello world" "2nd parameter" 34'
shlex.split(command)
['hello world', '2nd parameter', '34']
def generateOptionParser():
  '''Gets the return register as a string for lldb
    based upon the hardware
  '''
  usage = "usage: %prog [options] breakpoint_query\n" +\
          "Use 'bar -h' for option desc"
  # 1
  parser = optparse.OptionParser(usage=usage, prog='bar') 
  # 2
  parser.add_option("-n", "--non_regex",
                    # 3 
                    action="store_true",
                    # 4
                    default=False,
                    # 5
                    dest="non_regex",
                    # 6
                    help="Use a non-regex breakpoint instead")
  # 7
  return parser
command_args = shlex.split(command)
(options, args) = parser.parse_args(command_args)
options.non_regex
target = debugger.GetSelectedTarget()
breakpoint = target.BreakpointCreateByRegex(command)
'''Creates a regular expression breakpoint and adds it.
Once the breakpoint is hit, control will step out of the 
current function and print the return value. Useful for 
stopping on getter/accessor/initialization methods
'''

# 1
command = command.replace('\\', '\\\\')
# 2
command_args = shlex.split(command, posix=False)

# 3
parser = generateOptionParser()

# 4
try:
  # 5
  (options, args) = parser.parse_args(command_args)
except:
  result.SetError(parser.usage)
  return

target = debugger.GetSelectedTarget()

# 6
clean_command = shlex.split(args[0])[0]

# 7
if options.non_regex:
  breakpoint = target.BreakpointCreateByName(
                      clean_command)
else:
  breakpoint = target.BreakpointCreateByRegex(
                      clean_command)

# The rest remains unchanged

Testing out your first option

Enough code. Time to test this script out.

br dis 1
bar -n "-[NSUserDefaults(NSUserDefaults) objectForKey:]"

Adding options with params

You’ve learned how to add an option that expects no arguments. You’ll now add another option that expects a parameter. This next option will be the --module option to specify which module you want to constrain your regular expression query to.

# 1
parser.add_option("-m", "--module",
                  # 2
                  action="store",
                  # 3
                  default=None,
                  # 4
                  dest="module",
                  help="Filter a breakpoint by only searching within a specified Module")
if options.non_regex:
  breakpoint = target.BreakpointCreateByName(clean_command)
else:
  breakpoint = target.BreakpointCreateByRegex(clean_command)
if options.non_regex:
  breakpoint = target.BreakpointCreateByName(clean_command, options.module)
else:
  breakpoint = target.BreakpointCreateByRegex(clean_command, options.module)
(lldb) script help (lldb.SBTarget.BreakpointCreateByRegex)

BreakpointCreateByRegex(SBTarget self, str symbol_name_regex, str module_name=None) -> SBBreakpoint
bar @objc.*.init -m RWDevCon

(lldb) methods Person

Passing parameters into the breakpoint callback function

Time to create the parser option for -c, or --condition!

parser.add_option("-c", "--condition",
                  action="store",
                  default=None,
                  dest="condition",
                  help="Only stop if the expression matches True. Can reference return value through 'obj'. Obj-C only.")
breakpoint.SetScriptCallbackFunction("BreakAfterRegex.breakpointHandler")
def breakpointHandler(frame, bp_loc, dict):
  # method contents here
# 1
class BarOptions(object):

  # 2
  optdict = {}

  # 3
  @staticmethod
  def addOptions(options, breakpoint):
    key = str(breakpoint.GetID())
    BarOptions.optdict[key] = options
BarOptions.addOptions(options, breakpoint)
def evaluateCondition(debugger, condition):
  '''Returns True or False based upon the supplied condition.
  You can reference the NSObject through "obj"'''

  # 1
  res = lldb.SBCommandReturnObject()
  interpreter = debugger.GetCommandInterpreter()
  target = debugger.GetSelectedTarget()

  # 2
  expression = 'expression -lobjc -O -- id obj = ((id){}); ((BOOL){})'.format(getRegisterString(target), condition)
  interpreter.HandleCommand(expression, res)

  # 3
  if res.GetError():
    print(condition)
    print('*' * 80 + '\n' + res.GetError() + '\ncondition:' + condition)
    return False
  elif res.HasResult():
    # 4
    retval = res.GetOutput()

    # 5
    if 'YES' in retval:
      return True

  # 6
  return False
# 1
key = str(bp_loc.GetBreakpoint().GetID())
# 2
options = BarOptions.optdict[key]
# 3
if options.condition:
  # 4
  condition = shlex.split(options.condition)[0]
  # 5
  return evaluateCondition(debugger, condition)
bar NSURL\(.*init

bar NSURL\(.*init -c '(BOOL)[[obj absoluteString] containsString:@"amazon"]'

Where to go from here?

That was pretty intense, but you’ve learned how to incorporate options into your own Python scripts.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now