def sub_operations(jar, cf, instruction, operand, arg_names=[""]): """Gets the instrcutions of a different class""" invoked_class = operand.c name = operand.name descriptor = operand.descriptor args = method_descriptor(descriptor)[0] cache_key = "%s/%s/%s/%s" % (invoked_class, name, descriptor, _PIT.join(arg_names, ",")) if cache_key in _PIT.CACHE: cache = _PIT.CACHE[cache_key] operations = [op.clone() for op in cache] else: operations = _PIT.operations(jar, invoked_class, args, name, arg_names) position = 0 for operation in _PIT.ordered_operations(operations): position += 0.01 operation.position = instruction.pos + (position) _PIT.CACHE[cache_key] = operations return operations
def sub_operations(jar, cf, instruction, operand, arg_names=[""]): """Gets the instructions of a different class""" invoked_class = operand.c name = operand.name descriptor = operand.descriptor args = method_descriptor(descriptor)[0] cache_key = "%s/%s/%s/%s" % (invoked_class, name, descriptor, _PIT.join(arg_names, ",")) if cache_key in _PIT.CACHE: cache = _PIT.CACHE[cache_key] operations = [op.clone() for op in cache] else: operations = _PIT.operations(jar, invoked_class, args, name, arg_names) position = 0 for operation in _PIT.ordered_operations(operations): position += 0.01 operation.position = instruction.pos + (position) _PIT.CACHE[cache_key] = operations return operations
def operations(jar, classname, args=('java.io.DataOutputStream',), methodname=None, arg_names=("this", "stream")): """Gets the instructions of the specified method""" # Find the writing method cf = jar.open_class(classname) if methodname == None: method = cf.methods.find_one(args=args) else: method = cf.methods.find_one(name=methodname, args=args) if method == None: if cf.superclass: return _PIT.operations(jar, cf.superclass, args, methodname, arg_names) else: raise Exception("Call to unknown method") # Decode the instructions operations = [] stack = [] shortif_pos = -1 shortif_cond = '' for instruction in method.instructions: opcode = instruction.opcode operands = [InstructionField(operand, instruction, cf.constants) for operand in instruction.operands] # Shortcut if if instruction.pos == shortif_pos: category = stack[-1].category stack.append(Operand("((%(cond)s) ? %(sec)s : %(first)s)" % { "cond": shortif_cond, "first": stack.pop(), "sec": stack.pop() }, category)) # Method calls if opcode >= 0xb6 and opcode <= 0xb9: name = operands[0].name desc = operands[0].descriptor if name in _PIT.TYPES: operations.append(Operation(instruction.pos, "write", type=_PIT.TYPES[name], field=stack.pop())) stack.pop() elif name == "write": if desc.find("[BII") >= 0: stack.pop() stack.pop() operations.append(Operation( instruction.pos, "write", type="byte[]" if desc.find("[B") >= 0 else "byte", field=stack.pop())) stack.pop() elif desc == "(Ljava/lang/String;Ljava/io/DataOutputStream;)V": stack.pop() operations.append(Operation(instruction.pos, "write", type="string16", field=stack.pop())) else: descriptor = method_descriptor(desc) num_arguments = len(descriptor[0]) if num_arguments > 0: arguments = stack[-len(descriptor[0]):] else: arguments = [] for i in range(num_arguments): stack.pop() obj = "static" if opcode == 0xb8 else stack.pop() if descriptor[1] != "void": stack.append(Operand( "%s.%s(%s)" % ( obj, name, _PIT.join(arguments) ), 2 if descriptor[1] in ("long", "double") else 1) ) if "java.io.DataOutputStream" in descriptor[0]: operations += _PIT.sub_operations( jar, cf, instruction, operands[0], [obj] + arguments if obj != "static" else arguments ) # Conditional statements and loops elif opcode in [0xc7, 0xc6] or opcode >= 0x99 and opcode <= 0xa6: if opcode == 0xc7: condition = "%s == null" % stack.pop() elif opcode == 0xc6: condition = "%s != null" % stack.pop() else: if opcode <= 0x9e: # if comperation = opcode - 0x99 fields = [0, stack.pop()] elif opcode <= 0xa4: # if_icmp comperation = opcode - 0x9f fields = [stack.pop(), stack.pop()] else: # if_acmp comperation = opcode - 0xa5 fields = [operands.pop(), operands.pop()] if comperation == 0 and fields[0] == 0: condition = fields[1] else: condition = "%s %s %s" % ( fields[1], _PIT.CONDITIONS[comperation], fields[0] ) operations.append(Operation(instruction.pos, "if", condition=condition)) operations.append(Operation(operands[0].target, "endif")) shortif_cond = condition elif opcode == 0xaa: # tableswitch operations.append(Operation(instruction.pos, "switch", field=stack.pop())) low = operands[0].value[1] for opr in range(1, len(operands)): target = operands[opr].target operations.append(Operation(target, "case", value=low + opr - 1)) operations.append(Operation(operands[0].target, "endswitch")) elif opcode == 0xab: # lookupswitch operations.append(Operation(instruction.pos, "switch", field=stack.pop())) for opr in range(1, len(operands)): target = operands[opr].find_target(1) operations.append(Operation(target, "case", value=operands[opr].value[0])) operations.append(Operation(operands[0].target, "endswitch")) elif opcode == 0xa7: # goto target = operands[0].target endif = _PIT.find_next(operations, instruction.pos, "endif") case = _PIT.find_next(operations, instruction.pos, "case") if case != None and target > case.position: operations.append(Operation(instruction.pos, "break")) elif target > instruction.pos: endif.operation = "else" operations.append(Operation(target, "endif")) if len(stack) != 0: shortif_pos = target else: endif.operation = "endloop" _PIT.find_next(operations, target, "if").operation = "loop" # Math elif opcode >= 0x74 and opcode <= 0x77: category = stack[-1].category stack.append(Operand("(- %s)" % (stack.pop), category)) elif opcode >= 0x60 and opcode <= 0x7f: # Tneg lookup_opcode = opcode while not lookup_opcode in _PIT.MATH: lookop_opcode -= 1 category = stack[-1].category stack.append(Operand( "(%s %s %s)" % ( _PIT.MATH[lookup_opcode], stack.pop(), stack.pop() ), category )) elif opcode == 0x84: # iinc operations.append(Operation(instruction.pos, "increment", field="var%s" % operands[0], amount=operands[1])) # Other manually handled opcodes elif opcode == 0xc5: # multianewarray operand = "" for i in range(operands[1].value): operand = "[%s]%s" % (stack.pop(), operand) stack.append(Operand( "new %s%s" % (operands[0].type, operand))) elif opcode == 0x58: # pop2 if stack.pop().category != 2: stack.pop() elif opcode == 0x5f: # swap stack += [stack.pop(), stack.pop()] elif opcode == 0x59: # dup stack.append(stack[-1]) elif opcode == 0x5a: # dup_x1 stack.insert(-2, stack[-1]) elif opcode == 0x5b: # dup_x2 stack.insert(-2 if stack[-2].category == 2 else -3, stack[-1]) elif opcode == 0x5c: # dup2 if stack[-1].category == 2: stack.append(stack[-1]) else: stack += stack[-2:] elif opcode == 0x5d: # dup2_x1 if stack[-1].category == 2: stack.insert(-2, stack[-1]) else: stack.insert(-3, stack[-2]) stack.insert(-3, stack[-1]) elif opcode == 0x5e: # dup2_x2 if stack[-1].category == 2: stack.insert( -2 if stack[-2].category == 2 else -3, stack[-1] ) else: stack.insert( -3 if stack[-3].category == 2 else -4, stack[-2] ) stack.insert( -3 if stack[-3].category == 2 else -4, stack[-1] ) # Unhandled opcodes elif opcode in [0xc8, 0xa8, 0xc9]: raise Exception("unhandled opcode 0x%x" % opcode) # Default handlers else: lookup_opcode = opcode while not lookup_opcode in _PIT.OPCODES: lookup_opcode -= 1 handler = _PIT.OPCODES[lookup_opcode] index = 0 if isinstance(handler, int): handler = [handler] assert len(stack) >= handler[index] for i in range(handler[index]): operands.insert(0, stack.pop()) index += 1 if len(handler) > index: format = handler[index] index += 1 if (len(handler) > index and isinstance(handler[index], LambdaType)): value = handler[index](opcode) operands.append(value) operands.append(arg_names[value] if value < len(arg_names) else "var%s" % value) index += 1 elif len(operands) >= 1: value = operands[0].value operands.append(arg_names[value] if value < len(arg_names) else "var%s" % value) if (len(handler) > index and isinstance(handler[index], int)): category = handler[index] else: category = 1 stack.append(Operand( StringFormatter.format(format, *operands), category) ) return operations
def find_recipes(jar, cf, method, target_class, setter_names): # Temporary state variables tmp_recipes = [] started_item = False next_push_is_val = False with_metadata = False block_substitute = None block_subs = {} rows = [] make_count = 0 metadata = 0 recipe_target = None pushes_are_counts = False positions = True # Iterate over the methods instructions from the # bottom-up so we can catch the invoke/new range. for ins in method.instructions.reverse(): # Find the call that stores the generated recipe. if ins.name == "invokevirtual" and not started_item: const_i = ins.operands[0][1] const = cf.constants[const_i] # Parse the string method descriptor desc = const["name_and_type"]["descriptor"]["value"] args, returns = method_descriptor(desc) # We've found the recipe storage method if "java.lang.Object[]" in args and returns == "void": started_item = True pushes_are_counts = False next_push_is_val = False with_metadata = False rows = [] make_count = 0 metadata = 0 recipe_target = None method_name = const["name_and_type"]["name"]["value"] positions = method_name == setter_names[0] if positions: block_subs = {} else: block_subs = [] elif superclass in args: _class = const["class"]["name"]["value"] sub_cf = jar.open_class(_class) tmp_recipes += find_recipes( jar, sub_cf, sub_cf.methods.find_one(args=args), target_class, setter_names ) # We've found the start of the recipe declaration (or the end # as it is in our case). elif ins.name == "new" and started_item: started_item = False if positions: tmp_recipes.append({ "type": "shape", "substitutes": block_subs, "rows": rows, "makes": make_count, "recipe_target": recipe_target }) else: tmp_recipes.append({ "type": "shapeless", "ingredients": block_subs, "makes": make_count, "recipe_target": recipe_target }) # The item/block to be substituted elif (ins.name == "getstatic" and started_item and not pushes_are_counts): const_i = ins.operands[0][1] const = cf.constants[const_i] cl_name = const["class"]["name"]["value"] cl_field = const["name_and_type"]["name"]["value"] block_substitute = (cl_name, cl_field) if not positions: block_subs.append(block_substitute) elif ins.name == "getstatic" and pushes_are_counts: const_i = ins.operands[0][1] const = cf.constants[const_i] cl_name = const["class"]["name"]["value"] cl_field = const["name_and_type"]["name"]["value"] recipe_target = (cl_name, cl_field, metadata) # Block string substitute value elif ins.name == "bipush" and next_push_is_val: next_push_is_val = False block_subs[chr(ins.operands[0][1])] = block_substitute # Number of items that the recipe makes elif ins.name == "bipush" and pushes_are_counts: make_count = ins.operands[0][1] if with_metadata: metadata = make_count with_metadata = False elif ins.name.startswith("iconst_") and pushes_are_counts: make_count = int(ins.name[-1]) if with_metadata: metadata = make_count with_metadata = False # Recipe row elif ins.name == "ldc" and started_item: const_i = ins.operands[0][1] const = cf.constants[const_i] if const['tag'] == ConstantType.STRING: rows.append(const['string']['value']) # The Character.valueOf() call elif ins.name == "invokestatic": const_i = ins.operands[0][1] const = cf.constants[const_i] # Parse the string method descriptor desc = const["name_and_type"]["descriptor"]["value"] args, returns = method_descriptor(desc) if "char" in args and returns == "java.lang.Character": # The next integer push will be the character value. next_push_is_val = True elif ins.name == "invokespecial" and started_item: const_i = ins.operands[0][1] const = cf.constants[const_i] name = const["name_and_type"]["name"]["value"] if name == "<init>": pushes_are_counts = True if "II" in const["name_and_type"]["descriptor"]["value"]: with_metadata = True return tmp_recipes
def operations(jar, classname, args=None, methodname=None, arg_names=("this", "stream")): """Gets the instructions of the specified method""" # Find the writing method cf = jar.open_class(classname) if methodname is None and args is None: method = cf.methods.find_one(f=lambda m: m.args in ( ('java.io.DataOutputStream', ), ('java.io.DataOutput', ))) elif methodname is None: method = cf.methods.find_one(args=args) else: method = cf.methods.find_one(name=methodname, args=args) if method is None: if cf.superclass: return _PIT.operations(jar, cf.superclass, args, methodname, arg_names) else: raise Exception("Call to unknown method") # Decode the instructions operations = [] stack = [] skip_until = -1 shortif_pos = -1 shortif_cond = '' for instruction in method.instructions: if skip_until != -1: if instruction.pos == skip_until: skip_until = -1 else: continue opcode = instruction.opcode operands = [ InstructionField(operand, instruction, cf.constants) for operand in instruction.operands ] # Shortcut if if instruction.pos == shortif_pos: category = stack[-1].category stack.append( Operand( "((%(cond)s) ? %(sec)s : %(first)s)" % { "cond": shortif_cond, "first": stack.pop(), "sec": stack.pop() }, category)) # Method calls if opcode >= 0xb6 and opcode <= 0xb9: name = operands[0].name desc = operands[0].descriptor if name in _PIT.TYPES: operations.append( Operation(instruction.pos, "write", type=_PIT.TYPES[name], field=stack.pop())) stack.pop() elif name == "write": if desc.find("[BII") >= 0: stack.pop() stack.pop() operations.append( Operation( instruction.pos, "write", type="byte[]" if desc.find("[B") >= 0 else "byte", field=stack.pop())) stack.pop() elif desc in ( "(Ljava/lang/String;Ljava/io/DataOutputStream;)V", "(Ljava/lang/String;Ljava/io/DataOutput;)V"): stack.pop() operations.append( Operation(instruction.pos, "write", type="string16", field=stack.pop())) else: descriptor = method_descriptor(desc) num_arguments = len(descriptor[0]) if num_arguments > 0: arguments = stack[-len(descriptor[0]):] else: arguments = [] for i in range(num_arguments): stack.pop() obj = "static" if opcode == 0xb8 else stack.pop() if descriptor[1] != "void": stack.append( Operand( "%s.%s(%s)" % (obj, name, _PIT.join(arguments)), 2 if descriptor[1] in ("long", "double") else 1)) if ("java.io.DataOutputStream" in descriptor[0] or "java.io.DataOutput" in descriptor[0]): operations += _PIT.sub_operations( jar, cf, instruction, operands[0], [obj] + arguments if obj != "static" else arguments) # Conditional statements and loops elif opcode in [0xc7, 0xc6] or opcode >= 0x99 and opcode <= 0xa6: if opcode == 0xc7: condition = "%s == null" % stack.pop() elif opcode == 0xc6: condition = "%s != null" % stack.pop() else: if opcode <= 0x9e: # if comperation = opcode - 0x99 fields = [0, stack.pop()] elif opcode <= 0xa4: # if_icmp comperation = opcode - 0x9f fields = [stack.pop(), stack.pop()] else: # if_acmp comperation = opcode - 0xa5 fields = [operands.pop(), operands.pop()] if comperation == 0 and fields[0] == 0: condition = fields[1] else: condition = "%s %s %s" % ( fields[1], _PIT.CONDITIONS[comperation], fields[0]) operations.append( Operation(instruction.pos, "if", condition=condition)) operations.append(Operation(operands[0].target, "endif")) shortif_cond = condition elif opcode == 0xaa: # tableswitch operations.append( Operation(instruction.pos, "switch", field=stack.pop())) low = operands[0].value[1] for opr in range(1, len(operands)): target = operands[opr].target operations.append( Operation(target, "case", value=low + opr - 1)) operations.append(Operation(operands[0].target, "endswitch")) elif opcode == 0xab: # lookupswitch operations.append( Operation(instruction.pos, "switch", field=stack.pop())) for opr in range(1, len(operands)): target = operands[opr].find_target(1) operations.append( Operation(target, "case", value=operands[opr].value[0])) operations.append(Operation(operands[0].target, "endswitch")) elif opcode == 0xa7: # goto target = operands[0].target endif = _PIT.find_next(operations, instruction.pos, "endif") case = _PIT.find_next(operations, instruction.pos, "case") if case is not None and target > case.position: operations.append(Operation(instruction.pos, "break")) elif endif is not None: if target > instruction.pos: endif.operation = "else" operations.append(Operation(target, "endif")) if len(stack) != 0: shortif_pos = target else: endif.operation = "endloop" _PIT.find_next(operations, target, "if").operation = "loop" elif target > instruction.pos: skip_until = target # Math elif opcode >= 0x74 and opcode <= 0x77: category = stack[-1].category stack.append(Operand("(- %s)" % (stack.pop), category))
def find_recipes(jar, cf, method, target_class, setter_names): # Temporary state variables tmp_recipes = [] started_item = False next_push_is_val = False with_metadata = False block_substitute = None block_subs = {} rows = [] make_count = 0 metadata = 0 recipe_target = None pushes_are_counts = False positions = True # Iterate over the methods instructions from the # bottom-up so we can catch the invoke/new range. for ins in method.instructions.reverse(): # Find the call that stores the generated recipe. if ins.name == "invokevirtual" and not started_item: const_i = ins.operands[0][1] const = cf.constants[const_i] # Parse the string method descriptor desc = const["name_and_type"]["descriptor"]["value"] args, returns = method_descriptor(desc) # We've found the recipe storage method if "java.lang.Object[]" in args: started_item = True pushes_are_counts = False next_push_is_val = False with_metadata = False rows = [] make_count = 0 metadata = 0 recipe_target = None method_name = const["name_and_type"]["name"]["value"] positions = method_name == setter_names[0] if positions: block_subs = {} else: block_subs = [] elif superclass in args: _class = const["class"]["name"]["value"] sub_cf = jar.open_class(_class) tmp_recipes += find_recipes( jar, sub_cf, sub_cf.methods.find_one(args=args), target_class, setter_names) # We've found the start of the recipe declaration (or the end # as it is in our case). elif ins.name == "new" and started_item: started_item = False if positions: tmp_recipes.append({ "type": "shape", "substitutes": block_subs, "rows": rows, "makes": make_count, "recipe_target": recipe_target }) else: tmp_recipes.append({ "type": "shapeless", "ingredients": block_subs, "makes": make_count, "recipe_target": recipe_target }) # The item/block to be substituted elif (ins.name == "getstatic" and started_item and not pushes_are_counts): const_i = ins.operands[0][1] const = cf.constants[const_i] cl_name = const["class"]["name"]["value"] cl_field = const["name_and_type"]["name"]["value"] block_substitute = (cl_name, cl_field) if not positions: block_subs.append(block_substitute) elif ins.name == "getstatic" and pushes_are_counts: const_i = ins.operands[0][1] const = cf.constants[const_i] cl_name = const["class"]["name"]["value"] cl_field = const["name_and_type"]["name"]["value"] recipe_target = (cl_name, cl_field, metadata) # Block string substitute value elif ins.name == "bipush" and next_push_is_val: next_push_is_val = False block_subs[chr(ins.operands[0][1])] = block_substitute # Number of items that the recipe makes elif ins.name == "bipush" and pushes_are_counts: make_count = ins.operands[0][1] if with_metadata: metadata = make_count with_metadata = False elif ins.name.startswith("iconst_") and pushes_are_counts: make_count = int(ins.name[-1]) if with_metadata: metadata = make_count with_metadata = False # Recipe row elif ins.name == "ldc" and started_item: const_i = ins.operands[0][1] const = cf.constants[const_i] if const['tag'] == ConstantType.STRING: rows.append(const['string']['value']) # The Character.valueOf() call elif ins.name == "invokestatic": const_i = ins.operands[0][1] const = cf.constants[const_i] # Parse the string method descriptor desc = const["name_and_type"]["descriptor"]["value"] args, returns = method_descriptor(desc) if "char" in args and returns == "java.lang.Character": # The next integer push will be the character value. next_push_is_val = True elif ins.name == "invokespecial" and started_item: const_i = ins.operands[0][1] const = cf.constants[const_i] name = const["name_and_type"]["name"]["value"] if name == "<init>": pushes_are_counts = True if ("II" in const["name_and_type"]["descriptor"] ["value"]): with_metadata = True return tmp_recipes