add better support for r2frida, add ability to init state from frida hook

This commit is contained in:
aemmitt-ns committed 2020-11-19 11:21:08 -05:00
1 parent 16564f7a93
commit 68800dca69
7 files changed
+194 -18

No files matched your search

+1 -1
View File
@@ -214,7 +214,7 @@ class ESILRegisters:
return new_reg
def __contains__(self, key: str):
return self._registers.__contains__(key)
return (key in self._registers or key in self.aliases)
def clone(self):
clone = self.__class__(self.reg_info, self.aliases, self.pure_symbolic)
+27 -2
View File
@@ -25,7 +25,7 @@ class ESILSolver:
self.kwargs = kwargs
self.debug = kwargs.get("debug", False)
self.trace = kwargs.get("trace", False)
self.lazy = kwargs.get("lazy", False)
self.lazy = kwargs.get("lazy", False)
self.pcode = kwargs.get("pcode", False)
self.states = []
@@ -154,6 +154,10 @@ class ESILSolver:
""" End the execution """
self.stop = True
# just for r2frida, continue
def resume(self):
self.r2api.frida_continue()
# get rets from initial state to stop at
def default_avoid(self, state: ESILState):
pc = state.registers["PC"].as_long()
@@ -237,7 +241,28 @@ class ESILSolver:
# seek to function and init vm
self.r2api.seek(addr)
self.init_vm()
return self.init_state()
state = self.init_state()
# state.registers["PC"] = addr
return state
def frida_state(self, addr: Address) -> ESILState:
"""
Create an ESILState with PC at address from r2frida
:param addr: Name of symbol or address to begin execution
>>> state = esilsolver.frida_state("validate")
"""
if type(addr) == str:
addr = self.r2api.get_address(addr)
self.r2api.frida_init(addr)
state = self.init_state()
return state
def reset(self, state: ESILState = None):
"""
+95 -6
View File
@@ -1,5 +1,11 @@
import r2pipe
import binascii
import threading
try:
import frida
except ImportError:
pass
class R2API:
""" API for interacting with r2 through r2pipe """
@@ -18,11 +24,35 @@ class R2API:
self.instruction_cache = {}
self.cache_num = 64
self.sleep = 0.1
self.ccs = {}
self.register_info = None
self.get_register_info()
self.info = None
self.get_info()
info = self.r2p.cmdj("ij")
try:
self.frida = info["core"]["file"].startswith("frida:")
except:
self.frida = False
self.frida_sess = None
self.script = None
self.frida_sess_init()
def frida_sess_init(self):
if self.frida:
info = self.r2p.cmdj("ij")
self.pid = int(self.r2p.cmd("\\dp"))
self.device = frida.get_local_device()
for dev in frida.enumerate_devices():
if info["core"]["file"].startswith("frida://%s" % dev.id):
self.device = dev
self.frida_sess = self.device.attach(self.pid)
def get_info(self):
if self.info == None:
@@ -103,11 +133,11 @@ class R2API:
# theres no arj all function to get all the regs as json so i made this
# i should just make a pull request for r2
def get_all_registers(self):
def get_all_registers(self, thread=None):
reg_dict = {}
for reg in self.all_regs:
val_str = self.r2p.cmd("aer %s" % reg).strip().split(" = ")[-1]
val_str = self.r2p.cmd("aer %s" % reg).strip().split(" = ")[-1]
if val_str[:2] != "0x":
val_str = "0x0"
@@ -115,8 +145,32 @@ class R2API:
return reg_dict
def init_vm(self):
self.r2p.cmd("aei; aeim")
def init_vm(self, thread=None):
if not self.frida:
self.r2p.cmd("aeim")
else:
reg_dict = {}
reg_dicts = self.r2p.cmdj("\drj")
for rd in reg_dicts:
if thread == None or thread == rd["id"]:
reg_dict = rd["context"]
break
# .\dr* should do this but doesn't always work
for reg in reg_dict:
self.set_reg_value(reg, int(reg_dict[reg], 16))
self.r2p.cmd("aei; aeip") # set PC
def frida_init(self, addr):
self.disass(addr) # cache unhooked instrs
reg_dict = self.frida_context(addr)
# .\dr* should do this but doesn't always work
for reg in reg_dict:
self.set_reg_value(reg, int(reg_dict[reg], 16))
def emu(self, instr):
self.r2p.cmd("ae %s" % instr["esil"])
@@ -140,7 +194,42 @@ class R2API:
return self.ccs[func]
def get_address(self, func):
return self.r2p.cmdj("pdj 1 @ %s" % str(func))[0]["offset"]
if not self.frida:
return self.r2p.cmdj("pdj 1 @ %s" % str(func))[0]["offset"]
else:
return int(self.r2p.cmd("\isa %s" % str(func)), 16)
def analyze(self, level=3): # level 7 solves ctfs automatically
self.r2p.cmd("a"*level)
self.r2p.cmd("a"*level)
def frida_continue(self):
if not self.frida:
return
self.r2p.cmd("\\dc")
if self.script != None:
self.script.post({"type": "continue"})
self.script.unload()
self.script = None
def frida_context(self, addr):
# super jank
func = '''send(this.context);recv('continue',function(){}).wait()'''
script_data = '''Interceptor.attach(ptr('0x%x'),function(){%s})''' \
% (addr, func)
self.script = self.frida_sess.create_script(script_data)
context = {}
event = threading.Event()
def on_context(message, data):
if message["type"] == "send":
context.update(message["payload"])
event.set()
self.script.on('message', on_context)
self.script.load()
event.wait()
return context
+14
View File
@@ -0,0 +1,14 @@
from esilsolve import ESILSolver
import z3
esilsolver = ESILSolver("test/tests/validate", pcode=True)
state = esilsolver.call_state(0x1760)
addr = 0x100000
state.registers["r0"] = addr
flag = z3.BitVec("flag", 16*8)
state.memory[addr] = flag
state = esilsolver.run(target=0x00001840, avoid=[0x00001854])
flag_str = state.evaluate_buffer(flag).replace(b"\x00", b"")
print("FLAG: %s " % flag_str)
+24
View File
@@ -0,0 +1,24 @@
from esilsolve import ESILSolver
import z3
# start the ESILSolver instance by attaching r2frida (replace id with yours)
esilsolver = ESILSolver("frida://133ebc680e67c885e7f04621481d8a0229bef371/iOSCrackMe")
validate = esilsolver.r2api.get_address("validate")
# initialize state with context from hook, app is suspended
state = esilsolver.frida_state(validate)
# initialize symbolic bytes of solution
# and constrain them to be /[a-zA-Z]/
code = z3.BitVec("code", 16*8)
state.constrain_bytes(code, "[a-zA-Z]")
addr = state.registers["A0"].as_long()
state.memory[addr] = code
state = esilsolver.run(validate+0x210, avoid=[validate+0x218])
solution = state.evaluate_buffer(code)
print("CODE: '%s'" % solution.decode())
# write solution into proper place
# esilsolver.r2api.write(addr, solution)
esilsolver.resume() # resume suspended app
BIN
View File
Binary file not shown.
+33 -9
View File
@@ -5,8 +5,6 @@ try:
r2p = None
import os, sys
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
import esilsolve
import r2pipe
import z3
@@ -15,10 +13,12 @@ try:
import colorama
r2p = r2pipe.open()
except ImportError:
#print("esplugin could not be loaded")
except ImportError as e:
#print("esplugin could not be loaded: " + str(e))
pass
add_shortcuts = True
class ESILSolvePlugin:
def __init__(self, r2p):
@@ -28,6 +28,8 @@ class ESILSolvePlugin:
# aesx - Analysis: Emulation / Symbolic eXecution
"aesx?" : self.print_help,
"aesxi" : self.handle_init,
"aesxif": self.handle_init,
"aesxfc": self.handle_continue,
"aesxs" : self.handle_set_symbolic,
"aesxsb": self.handle_set_symbolic,
"aesxsc": self.handle_set_symbolic,
@@ -52,6 +54,13 @@ class ESILSolvePlugin:
"aesxws": self.handle_state_set
}
if add_shortcuts:
new = {}
for c in self.commands:
new["X"+c[4:]] = self.commands[c]
self.commands.update(new)
self.symbols = {}
self.esinstance = None
self.initialized = False
@@ -78,13 +87,14 @@ class ESILSolvePlugin:
lines = [
usage,
["aesxi", " [debug] [lazy]", "Initialize the ESILSolve instance and VM"],
["aesxi", "[f] [debug] [lazy]", "Initialize the ESILSolve instance and VM"],
["aesxs", "[bc] reg|addr [name] [length]", "Set symbolic value in register or memory"],
["aesxv", " reg|addr value", "Set concrete value in register or memory"],
["aesxc", " sym value", "Constrain symbol to be value, min, max, regex"],
["aesxc", "[+-]", "Push / pop the constraint context"],
["aesxx", "[ec] expr value", "Execute ESIL expression and evaluate/constrain the result"],
["aesxr", "[ac] target [avoid x,y,z]", "Run symbolic execution until target address, avoiding x,y,z"],
["aesxf", "[c]", "Resume r2frida after symex is finished"],
["aesxe", "[j] sym1 [sym2] [...]", "Evaluate symbol in current state"],
["aesxb", "[j] sym1 [sym2] [...]", "Evaluate buffer in current state"],
["aesxd", "[j] [reg1] [reg2] [...]", "Dump register values / ASTs"],
@@ -99,14 +109,25 @@ class ESILSolvePlugin:
lazy = "lazy" in args
self.esinstance = esilsolve.ESILSolver(self.r2p, debug=debug, lazy=lazy)
core = self.r2p.cmdj("ij")["core"]
if "referer" not in core and "frida:" != core["file"][:7]:
self.esinstance.r2api.init_vm()
self.state = self.esinstance.init_state()
if args[0][-1] != "f":
thread = None
if len(args) > 1 and is_int(args[1]):
thread = to_int(args[1])
self.esinstance.r2api.init_vm(thread)
self.state = self.esinstance.init_state()
else:
pc = to_int(self.r2p.cmd("s"))
self.state = self.esinstance.frida_state(pc)
self.initialized = True
self.symbols = {}
def handle_continue(self, args):
self.esinstance.resume()
def handle_apply(self, args):
if not self.initialized:
self.print("error: need to initialize first")
@@ -420,6 +441,9 @@ def to_int(s):
return int(s)
def is_int(s):
if s[:1] == "-":
s = s[1:]
if s.isdigit() or s[:2] == "0x":
return True