192 lines
6.1 KiB
Python
192 lines
6.1 KiB
Python
"""Exercise the real bridge instruction walker with Hopper-shaped objects."""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
|
|
class Block:
|
|
def __init__(self, key, start, end, members):
|
|
self.key, self.start, self.end, self.members = key, start, end, members
|
|
|
|
def __eq__(self, other):
|
|
return isinstance(other, Block) and self.key == other.key
|
|
|
|
def __hash__(self):
|
|
return hash(self.key)
|
|
|
|
def getStartingAddress(self):
|
|
return self.start
|
|
|
|
def getEndingAddress(self):
|
|
return self.end
|
|
|
|
def getSuccessorCount(self):
|
|
return 0
|
|
|
|
|
|
class Instruction:
|
|
def __init__(self, mnemonic, length=4):
|
|
self.mnemonic, self.length = mnemonic, length
|
|
|
|
def getInstructionLength(self):
|
|
return self.length
|
|
|
|
def getInstructionString(self):
|
|
return self.mnemonic
|
|
|
|
def getArgumentCount(self):
|
|
return 0
|
|
|
|
|
|
class Segment:
|
|
def __init__(self, instructions):
|
|
self.instructions = instructions
|
|
|
|
def getInstructionAtAddress(self, address):
|
|
return self.instructions.get(address)
|
|
|
|
def getNameAtAddress(self, address):
|
|
return "fixture"
|
|
|
|
def getReferencesFromAddress(self, address):
|
|
return [0x9000] if self.instructions[address].mnemonic.startswith("b") else []
|
|
|
|
def getReferencesOfAddress(self, address):
|
|
return []
|
|
|
|
def getCommentAtAddress(self, address):
|
|
return None
|
|
|
|
def getInlineCommentAtAddress(self, address):
|
|
return None
|
|
|
|
|
|
class Procedure:
|
|
def __init__(self, blocks, segment):
|
|
self.blocks, self.segment = blocks, segment
|
|
|
|
def basicBlockIterator(self):
|
|
return iter(self.blocks)
|
|
|
|
def getBasicBlockAtAddress(self, address):
|
|
for block in self.blocks:
|
|
if address in block.members:
|
|
# Hopper returns fresh wrappers whose native handles compare equal.
|
|
return Block(block.key, block.start, block.end, block.members)
|
|
return None
|
|
|
|
def getBasicBlockCount(self):
|
|
return len(self.blocks)
|
|
|
|
def getAllCallees(self):
|
|
return []
|
|
|
|
def getAllCallers(self):
|
|
return []
|
|
|
|
def getSegment(self):
|
|
return self.segment
|
|
|
|
def getEntryPoint(self):
|
|
return self.blocks[0].start
|
|
|
|
def signatureString(self):
|
|
return "int fixture()"
|
|
|
|
def getLocalVariableList(self):
|
|
return []
|
|
|
|
def getAllCallerProcedures(self):
|
|
return []
|
|
|
|
def getAllCalleeProcedures(self):
|
|
return []
|
|
|
|
def decompile(self):
|
|
return "fixture pseudocode"
|
|
|
|
|
|
def main():
|
|
path = sys.argv[1]
|
|
bridge = {"__file__": path, "__name__": "rea_hopper_bridge"}
|
|
exec(compile(Path(path).read_text(encoding="utf-8"), path, "exec"), bridge)
|
|
scenario = sys.argv[2]
|
|
instructions = {0x1000: Instruction("mov"), 0x1004: Instruction("b.ne"),
|
|
0x1008: Instruction("ret"), 0x100C: Instruction("nop")}
|
|
if scenario == "inclusive":
|
|
blocks = [Block(0, 0x1000, 0x1004, {0x1000, 0x1004}),
|
|
Block(1, 0x1008, 0x1008, {0x1008})]
|
|
elif scenario == "exclusive":
|
|
blocks = [Block(0, 0x1000, 0x1008, {0x1000, 0x1004}),
|
|
Block(1, 0x1008, 0x100C, {0x1008})]
|
|
elif scenario == "duplicate":
|
|
block = Block(0, 0x1000, 0x1008, {0x1000, 0x1004, 0x1008})
|
|
blocks = [block, block]
|
|
elif scenario == "gap":
|
|
instructions.pop(0x1004)
|
|
blocks = [Block(0, 0x1000, 0x1008, {0x1000, 0x1004, 0x1008})]
|
|
elif scenario == "zero_length":
|
|
instructions[0x1004] = Instruction("invalid", 0)
|
|
blocks = [Block(0, 0x1000, 0x1008, {0x1000, 0x1004, 0x1008})]
|
|
elif scenario == "negative_length":
|
|
instructions[0x1004] = Instruction("invalid", -4)
|
|
blocks = [Block(0, 0x1000, 0x1008, {0x1000, 0x1004, 0x1008})]
|
|
elif scenario == "cross_boundary":
|
|
instructions[0x1000] = Instruction("invalid", 8)
|
|
blocks = [Block(0, 0x1000, 0x1004, {0x1000})]
|
|
elif scenario == "foreign_owner":
|
|
blocks = [Block(0, 0x1000, 0x100C, {0x1000, 0x1004})]
|
|
elif scenario == "reversed":
|
|
blocks = [Block(0, 0x1008, 0x1000, {0x1008})]
|
|
elif scenario == "variable_length":
|
|
instructions = {0x1000: Instruction("mov", 3),
|
|
0x1003: Instruction("b.ne", 2),
|
|
0x1005: Instruction("ret", 1),
|
|
0x1006: Instruction("nop", 1)}
|
|
blocks = [Block(0, 0x1000, 0x1003, {0x1000, 0x1003}),
|
|
Block(1, 0x1005, 0x1005, {0x1005})]
|
|
elif scenario == "no_ownership":
|
|
blocks = [Block(0, 0x1000, 0x1004, {0x1000})]
|
|
else:
|
|
raise ValueError("Unknown scenario")
|
|
segment = Segment(instructions)
|
|
procedure = Procedure(blocks, segment)
|
|
if scenario == "no_ownership":
|
|
procedure.getBasicBlockAtAddress = None
|
|
try:
|
|
bridge["_instruction_addresses"](procedure)
|
|
except bridge["CapabilityUnavailableError"] as error:
|
|
print(json.dumps({"error": str(error)}))
|
|
return
|
|
raise AssertionError("Missing ownership API was silently accepted")
|
|
class API:
|
|
def require_analysis_complete(self, document, method):
|
|
pass
|
|
|
|
class Document:
|
|
def getSegmentsList(self):
|
|
return []
|
|
|
|
document = Document()
|
|
bridge["_api"] = lambda: API()
|
|
bridge["_document"] = lambda name=None: document
|
|
bridge["_procedure"] = lambda document, value=None: procedure
|
|
bridge["_segment"] = lambda document, address: segment
|
|
bridge["_containing_procedure"] = lambda document, address: (None, None)
|
|
bridge["_search_inventory"] = lambda document, kind: []
|
|
dossier = bridge["_analyze_function"](document, {})
|
|
print(json.dumps({
|
|
"blocks": dossier["basic_blocks"],
|
|
"length": bridge["_dispatch"]("procedure_info", {})["length"],
|
|
"addresses": bridge["_instruction_addresses"](procedure),
|
|
"assembly": bridge["_assembly"](procedure).splitlines(),
|
|
"fast": bridge["_read_function_instructions"](document, {})["instructions"],
|
|
"dossier": dossier["assembly"],
|
|
"references": bridge["_procedure_references"](document, {})["references"],
|
|
}))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|