I don’t know if you figure it out, but if someone is getting the same error. Is due to context variable clashing with other environment variable called context. And there’s another error in the script missing a ;.
Line 158:
unsigned int maxresults = ''' + str(options.max_results) + r''';
What I did to fix the context issue was to change the name of the variable from context to djrc_context.
And that’s it,
Derik
Next you’ll see the complete file with the changes I mentioned.
============= serach.py ==========
# Credit where credit is due.
# This script was inspired by libBeagle https://github.com/heardrwt/RHObjectiveBeagle
# which in turn was inspired by Saurik's 'choose' command in cycript http://www.cycript.org/
# which in turn was inspired by Apple's heap python script 'command script import lldb.macosx.heap'
# which (I think) in turn was inspired by Apple's heap_find.cpp sourcefile found here
# https://opensource.apple.com/source/lldb/lldb-179.1/examples/darwin/heap_find/heap/heap_find.cpp
# All have made great progress in their own right.
# This tool improves upon its predecessors by adding options that lldb can use to filter queries
# For exmple, filtering all NSObject subclasses found within a given dynamic library
# i.e search NSObject -m UIKit
# MIT License
#
# Copyright (c) 2018 Derek Selander
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import lldb
import os
import shlex
import optparse
import lldb.utils.symbolication
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f search.search search')
def search(debugger, command, result, internal_dict):
'''
Finds all subclasses of a class. This class must by dynamic
(aka inherit from a NSObject class). Currently doesn't work
with NSString or NSNumber (tagged pointer objects).
NOTE: This script will leak memory
Examples:
# Find all UIViews and subclasses of UIViews
find UIView
# Find all UIStatusBar instances
find UIStatusBar
# Find all UIViews, ignore subclasses
find UIView -e
# Find all instances of UIViews (and subclasses) where tag == 5
find UIView -c "[obj tag] == 5"
'''
command_args = shlex.split(command)
parser = generate_option_parser()
try:
(options, args) = parser.parse_args(command_args)
except:
result.SetError(parser.usage)
return
if not args:
result.SetError('Usage: find NSObjectSubclass\n\nUse \'help find\' for more details')
return
clean_command = ('').join(args)
res = lldb.SBCommandReturnObject()
interpreter = debugger.GetCommandInterpreter()
if options.module:
target = debugger.GetSelectedTarget()
module = target.FindModule(lldb.SBFileSpec(options.module))
if not module.IsValid():
result.SetError(
"Unable to open module name '{}', to see list of images use 'image list -b'".format(options.module))
return
options.module = generate_module_search_sections_string(module, target)
interpreter.HandleCommand('po (Class)NSClassFromString(@\"{}\")'.format(clean_command), res)
if 'nil' in res.GetOutput():
result.SetError('Can\'t find class named "{}". Womp womp...'.format(clean_command))
return
objectiveC_class = 'NSClassFromString(@"{}")'.format(clean_command)
command_script = get_command_script(objectiveC_class, options)
expr_options = lldb.SBExpressionOptions()
expr_options.SetIgnoreBreakpoints(True);
expr_options.SetFetchDynamicValue(lldb.eNoDynamicValues);
expr_options.SetTimeoutInMicroSeconds (30*1000*1000) # 30 second timeout
expr_options.SetTryAllThreads (True)
expr_options.SetUnwindOnError(True)
expr_options.SetGenerateDebugInfo(True)
expr_options.SetLanguage (lldb.eLanguageTypeObjC_plus_plus)
expr_options.SetCoerceResultToId(True)
# expr_options.SetAutoApplyFixIts(True)
frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
if frame is None:
result.SetError('You must have the process suspended in order to execute this command')
return
# debugger.HandleCommand('po ' + command_script)
#import pdb; pdb.set_trace()
# debugger.HandleCommand('expression -lobjc++ -g -O -- ' + command_script)
expr_sbvalue = frame.EvaluateExpression (command_script, expr_options)
count = expr_sbvalue.GetNumChildren() # Actually goes up to 2^32 but this is more than enough
if not expr_sbvalue.error.success:
result.SetError("\n***************************************\nerror: " + str(expr_sbvalue.error) + command_script)
else:
if count > 1000:
result.AppendWarning('Exceeded 1000 hits, try narrowing your search with the --condition option')
result.AppendMessage (expr_sbvalue)
else:
if options.barebones:
for val in expr_sbvalue:
val_description = str(val.GetTypeName()) + ' [' + str(val.GetValue()) + ']'
result.AppendMessage(val_description)
else:
result.AppendMessage(expr_sbvalue.description)
def get_command_script(objectiveC_class, options):
command_script = r'''//grab the zones in the current process
@import Foundation;
@import ObjectiveC;
typedef struct _DSSearchContext {
Class query;
CFMutableSetRef classesSet;
CFMutableSetRef results;
} DSSearchContext;
auto task_peek = [](task_t task, vm_address_t remote_address, vm_size_t size, void **local_memory) -> kern_return_t {
*local_memory = (void*) remote_address;
return (kern_return_t)0;
};
vm_address_t *zones = NULL;
unsigned int count = 0;
unsigned int maxresults = ''' + str(options.max_results) + r''';
kern_return_t error = (kern_return_t)malloc_get_all_zones(0, 0, &zones, &count);
DSSearchContext *djrc_context = (DSSearchContext *)calloc(sizeof(DSSearchContext), 1);
int classCount = (int)objc_getClassList(NULL, 0);
CFMutableSetRef set = (CFMutableSetRef)CFSetCreateMutable(0, classCount, NULL);
Class *classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * classCount);
objc_getClassList(classes, classCount);
typedef struct malloc_introspection_t {
kern_return_t (*enumerator)(task_t task, void *, unsigned type_mask, vm_address_t zone_address, memory_reader_t reader, vm_range_recorder_t recorder);
} malloc_introspection_t;
typedef struct malloc_zone_t {
void *reserved1[9];
const char *zone_name;
void *reserved2[2];
struct malloc_introspection_t *introspect;
unsigned version;
void *reserved3[3];
} malloc_zone_t;
for (int i = 0; i < classCount; i++) {
Class cls = classes[i];
CFSetAddValue(set, (__bridge const void *)(cls));
}
// Setup callback djrc_context
djrc_context->results = (CFMutableSetRef)CFSetCreateMutable(0, maxresults, NULL);
djrc_context->classesSet = set;
djrc_context->query = ''' + objectiveC_class + r''';
for (unsigned i = 0; i < count; i++) {
const malloc_zone_t *zone = (const malloc_zone_t *)zones[i];
if (zone == NULL || zone->introspect == NULL){
continue;
}
//for each zone, enumerate using our enumerator callback
zone->introspect->enumerator(0, djrc_context, 1, zones[i], task_peek,
[] (task_t task, void *baton, unsigned type, vm_range_t *ranges, unsigned count) -> void {
DSSearchContext *djrc_context = (DSSearchContext *)baton;
Class query = djrc_context->query;
CFMutableSetRef classesSet = djrc_context->classesSet;
CFMutableSetRef results = djrc_context->results;
int maxCount = ''' + str(options.max_results) + ''';
size_t querySize = (size_t)class_getInstanceSize(query);
int (^isBlackListClass)(Class) = ^int(Class aClass) {
NSString *className = (NSString *)NSStringFromClass(aClass);
if ([@"_NSZombie_" isEqualToString:className]) return 1;
if ([@"NSPlaceholderMutableString" isEqualToString:className]) return 1;
if ([@"__ARCLite__" isEqualToString:className]) return 1;
if ([@"__NSCFCalendar" isEqualToString:className]) return 1;
if ([@"__NSCFTimer" isEqualToString:className]) return 1;
if ([@"NSCFTimer" isEqualToString:className]) return 1;
if ([@"__NSMessageBuilder" isEqualToString:className]) return 1;
if ([@"__NSGenericDeallocHandler" isEqualToString:className]) return 1;
if ([@"NSTaggedPointerStringCStringContainer" isEqualToString:className]) return 1;
if ([@"NSAutoreleasePool" isEqualToString:className]) return 1;
if ([@"NSPlaceholderNumber" isEqualToString:className]) return 1;
if ([@"NSPlaceholderString" isEqualToString:className]) return 1;
if ([@"NSPlaceholderValue" isEqualToString:className]) return 1;
if ([@"Object" isEqualToString:className]) return 1;
if ([@"NSPlaceholderNumber" isEqualToString:className]) return 1;
if ([@"VMUArchitecture" isEqualToString:className]) return 1;
if ([className hasPrefix:@"__NSPlaceholder"]) return 1;
return 0;
};
for (int i = 0; i < count; i++) {
if (CFSetGetCount(results) >= maxCount) {
break;
}
// test 1
if (ranges[i].size < querySize) {
continue;
}
vm_address_t potentialObject = ranges[i].address;
Class potentialClass = object_getClass((__bridge id)((void *)potentialObject));
// test 2
if (!(int)CFSetContainsValue(classesSet, (__bridge const void *)(potentialClass))) {
continue;
}
// test 3
if ((size_t)malloc_good_size((size_t)class_getInstanceSize(potentialClass)) != ranges[i].size) {
continue;
}
// Yay, if we are here this is likely an NSObject
if (isBlackListClass(potentialClass)) {
continue;
}
id obj = (__bridge id)(void *)potentialObject;
if (!(BOOL)[obj respondsToSelector:@selector(description)]) {
continue;
}
'''
if options.exact_match:
command_script += 'if ((int)[potentialClass isMemberOfClass:query]'
else:
command_script += 'if ((int)[potentialClass isSubclassOfClass:query]'
if options.condition:
cmd = options.condition
command_script += '&& (int)(' + options.condition + ')'
command_script += r') {'
if options.module:
command_script += options.module
command_script += r'''
CFSetAddValue(results, (__bridge const void *)(obj));
}
}
});
}
CFIndex index = (CFIndex)CFSetGetCount(djrc_context->results);
const void **values = (const void **)calloc(index, sizeof(id));
CFSetGetValues(djrc_context->results, values);
NSMutableArray *outputArray = [NSMutableArray arrayWithCapacity:index];
for (int i = 0; i < index; i++) {
id object = (__bridge id)(values[i]);
[outputArray addObject:object];
}
free(values);
free(set);
free(djrc_context);
free(classes);'''
if options.perform_action:
command_script += r'''
[outputArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){;
'''
command_script += options.perform_action + ' }];\n'
command_script += 'outputArray;'
return command_script
def generate_module_search_sections_string(module, target):
returnString = r'''
uintptr_t addr = (uintptr_t)potentialClass;
if (!('''
section = module.FindSection("__DATA")
for idx, subsec in enumerate(section):
lower_bounds = subsec.GetLoadAddress(target)
upper_bounds = lower_bounds + subsec.file_size
if idx != 0:
returnString += ' || '
returnString += '({} <= addr && addr <= {})'.format(lower_bounds, upper_bounds)
dirtysection = module.FindSection("__DATA_DIRTY")
for subsec in dirtysection:
lower_bounds = subsec.GetLoadAddress(target)
upper_bounds = lower_bounds + subsec.file_size
returnString += ' || ({} <= addr && addr <= {})'.format(lower_bounds, upper_bounds)
returnString += ')) { continue; }\n'
return returnString
def generate_option_parser():
usage = "usage: %prog [options] NSObjectSubclass"
parser = optparse.OptionParser(usage=usage, prog="search")
parser.add_option("-e", "--exact_match",
action="store_true",
default=False,
dest="exact_match",
help="Searches for exact matches of class, i.e. no subclasses")
parser.add_option("-c", "--condition",
action="store",
default=None,
dest="condition",
help="a conditional expression to filter hits. Objective-C input only. Use 'obj' to reference object")
parser.add_option("-p", "--perform-action",
action="store",
default=None,
dest="perform_action",
help="Perform an action on every returned query. Objective-C input only. Use 'obj' to reference object")
parser.add_option("-m", "--module",
action="store",
default=None,
dest="module",
help="Filters results to only be in a certain module. i.e. -m UIKit")
parser.add_option("-b", "--barebones",
action="store_true",
default=False,
dest="barebones",
help="Only dump out the classname and pointer, no description")
parser.add_option("-x", "--max_results",
action="store",
default=200,
type="int",
dest="max_results",
help="Specifies the maximum return count that the script should return")
return parser