From 2d998b1cefd20529bebb1688481f9f3da8fcb979 Mon Sep 17 00:00:00 2001 From: Bjoern Kerler Date: Wed, 22 Aug 2018 23:34:17 +0200 Subject: [PATCH 1/5] Fixes and python3 support --- __init__.py | 16 ++++---- analysis_engine.py | 48 +++++++++++----------- cli_ui.py | 2 +- codegen.py | 62 +++++++++++++++++++++-------- conScan.py | 8 ++-- defunct/.widgets.py.swp | Bin 12288 -> 0 bytes dependency.py | 20 +++++----- gui.py | 13 +++--- packager.py | 48 +++++++++++----------- r2pipe_run.py | 10 ++--- sample/impCall/ripr_arm_impCall.py | 12 +++--- sample/impCall/ripr_x64_impCall.py | 12 +++--- sample/impCall/ripr_x86_impCall.py | 12 +++--- sample/multiFunc/demo.py | 18 ++++----- sample/rc4/myrc4.py | 4 +- sample/rc4/tester.py | 6 +-- 16 files changed, 161 insertions(+), 130 deletions(-) delete mode 100644 defunct/.widgets.py.swp diff --git a/__init__.py b/__init__.py index 4291997..0942a79 100644 --- a/__init__.py +++ b/__init__.py @@ -1,6 +1,6 @@ import sys if (sys.platform == 'win32'): - sys.path.append("C:\\Python27\\lib\\site-packages\\PyQt5") + sys.path.append("C:\\Python37\\lib\\site-packages\\PyQt5") try: from binaryninja import * @@ -9,10 +9,10 @@ raise ImportError # ripr imports - from analysis_engine import * - from codegen import * - from packager import * - import gui + from .analysis_engine import * + from .codegen import * + from .packager import * + from .gui import * ui = gui.riprWidget() @@ -22,19 +22,19 @@ def packageFunction(view, fobj): pkg.package_function() def packageRegion(view, start, length): - print "[ripr] Packaging 0x%x - 0x%x" % (start, start+length) + print ("[ripr] Packaging 0x%x - 0x%x" % (start, start+length)) engine = get_engine(view) pkg = Packager(isFunc=False, address=start, engine=engine, length=length, ui=ui) pkg.package_region() def packageBasicBlock(view, addr): - print "[ripr] Adding Basic Block containing %x " % addr + print ("[ripr] Adding Basic Block containing %x " % addr) engine = get_engine(view) pkg = Packager(isFunc=False, address=addr, engine=engine, ui=ui) pkg.package_bb() def generate_basicBlocks(view, fobj): - print "[ripr] Generating code from currently selected basic blocks" + print ("[ripr] Generating code from currently selected basic blocks") engine = get_engine(view) pkg = Packager(isFunc=False, address=fobj.start, engine=engine, ui=ui) pkg.generate_bb_code() diff --git a/analysis_engine.py b/analysis_engine.py index 59e30d5..e542a65 100644 --- a/analysis_engine.py +++ b/analysis_engine.py @@ -7,17 +7,17 @@ try: from binaryninja import * except: - print "[!!] Not running in Binary Ninja" + print ("[!!] Not running in Binary Ninja") try: import r2pipe except: - print "[!!] Not running in Radare2" + print ("[!!] Not running in Radare2") import json import re import sys -import codegen +from .codegen import * from binascii import unhexlify def get_engine(*args): @@ -31,7 +31,7 @@ def get_engine(*args): return bn_engine(args[0]) - raise ValueError, "No analysis engine found!" + raise (ValueError, "No analysis engine found!") @@ -160,7 +160,7 @@ def get_arch(self): These can be different from Binary Ninja names, so they are explicitly mapped into the ripr names, even if they are the same in some cases. ''' - print self.bv.arch.name + print (self.bv.arch.name) if (self.bv.arch.name == 'x86'): return 'x86' elif (self.bv.arch.name == 'x86_64'): @@ -171,7 +171,7 @@ def get_arch(self): def mark_gathered_basic_block(self, address): fobj = self.bv.get_functions_containing(address)[0] if (fobj == None): - print "FOBJ IS NONE" + print ("FOBJ IS NONE") bb = fobj.get_basic_block_at(address) bb.highlight = HighlightStandardColor.BlackHighlightColor @@ -187,24 +187,24 @@ def clean_gathered_basic_block(self, address): def get_basic_block_bytes(self, address): bb = self.bv.get_basic_blocks_at(address) if len(bb) != 1: - print "[ripr] Address belongs to more than one basic block!" + print ("[ripr] Address belongs to more than one basic block!") bb = bb[0] - return {bb.start: codegen.codeSlice(self.read_bytes(bb.start, bb.end-bb.start), bb.start)} + return {bb.start: codeSlice(self.read_bytes(bb.start, bb.end-bb.start), bb.start)} def get_function_bytes(self, address=None, name=None): ''' Binary Ninja does not seem to assume Functions are contiguous; rather they are treated as a collection of basic blocks. ''' - print "[ripr] Inside get_function_bytes()" + print ("[ripr] Inside get_function_bytes()") if (address != None): fobj = self.bv.get_function_at(address) elif (name != None): - print "[ripr] TODO" + print ("[ripr] TODO") return else: - print "[ripr] No arguments supplied to get_function_bytes" + print ("[ripr] No arguments supplied to get_function_bytes") return None # Sort the basic blocks in ascending order bblist = sorted(fobj.basic_blocks, key=lambda x: x.start) @@ -218,10 +218,10 @@ def get_function_bytes(self, address=None, name=None): clist.append([bb]) # Print out the list if the function is not contiguous if (len(clist) > 1): - print clist + print (clist) # Create a return list in the expected format from the contiguous units. - retdir = {unit[0].start : codegen.codeSlice(self.read_bytes(unit[0].start, unit[-1].start - unit[0].start + unit[-1].length), unit[0].start) for unit in clist} + retdir = {unit[0].start : codeSlice(self.read_bytes(unit[0].start, unit[-1].start - unit[0].start + unit[-1].length), unit[0].start) for unit in clist} return retdir def get_page_bytes(self, address): @@ -264,7 +264,7 @@ def get_instruction_length(self, address): def find_llil_block_from_addr(self, address): fobj = self.bv.get_functions_containing(address) if len(fobj) > 1: - print "[ripr] Multiple Functions contain this address!!" + print ("[ripr] Multiple Functions contain this address!!") return None fobj = fobj[0] bbindex = fobj.get_basic_block_at(address).index @@ -273,7 +273,7 @@ def find_llil_block_from_addr(self, address): def find_mlil_block_from_addr(self, address): fobj = self.bv.get_functions_containing(address) if len(fobj) > 1: - print "[ripr] Multiple Functions contain this address!!" + print ("[ripr] Multiple Functions contain this address!!") return None fobj = fobj[0] bbindex = fobj.get_basic_block_at(address).index @@ -381,7 +381,7 @@ def highlight_instr(self, func_addr, instrAddr, color): elif color == "orange": bn_color = HighlightStandardColor.OrangeHighlightColor else: - raise ValueError, "Unsupported color" + raise (ValueError, "Unsupported color") fobj.set_user_instr_highlight(instrAddr, bn_color) def add_comment(self, func_addr, instrAddr, comment): @@ -442,29 +442,29 @@ def get_arch(self): elif arch == "x86" and bits == 64: return 'x64' else: - raise NotImplementedError, "Only tested witn x86 & x86_64" + raise (NotImplementedError, "Only tested witn x86 & x86_64") def get_function_bytes(self, address=None, name=None): if (address != None): funcInfo = self.r2.cmd("afij {}".format(hex(address))) elif (name != None): - print "[ripr] TODO" + print ("[ripr] TODO") return else: - print "[ripr] No arguments supplied to get_function_bytes" + print ("[ripr] No arguments supplied to get_function_bytes") return None if funcInfo.strip() == "": - raise ValueError, "Function not found at {}".format(address) + raise (ValueError, "Function not found at {}".format(address)) funcInfo = json.loads(funcInfo, strict=False) if len(funcInfo) == 0: - raise ValueError, "Function not found at {}".format(address) - print funcInfo + raise (ValueError, "Function not found at {}".format(address)) + print (funcInfo) offset = funcInfo[0]["offset"] size = funcInfo[0]["size"] bytes = self.read_bytes(offset, size) - retdir = {offset: codegen.codeSlice(bytes, offset)} + retdir = {offset: codeSlice(bytes, offset)} return retdir def get_page_bytes(self, address): @@ -560,7 +560,7 @@ def highlight_instr(self, func_addr, instrAddr, color): def add_comment(self, func_addr, instrAddr, comment): if not re.compile("^[a-z0-9 !\\-\\_]+$", re.IGNORECASE).match(comment): # Don't send arbitrary contents to radare pipe - print "Ignoring malformed comment: {}".format(comment) + print ("Ignoring malformed comment: {}".format(comment)) else: self.r2.cmd("CC [ripr] {} @{}".format(comment, hex(instrAddr))) diff --git a/cli_ui.py b/cli_ui.py index 39900ac..6255ab6 100644 --- a/cli_ui.py +++ b/cli_ui.py @@ -23,7 +23,7 @@ def text_input_box(self, promptText): def impCallsOptions(self): options = ["nop", "hook", "cancel"] - print "Code contains calls to imported functions. How should this be handled?", + print ("Code contains calls to imported functions. How should this be handled?") while True: selection = raw_input("({})?".format(", ".join(options))).strip() if selection in options: diff --git a/codegen.py b/codegen.py index 07f12ae..f0cf129 100644 --- a/codegen.py +++ b/codegen.py @@ -3,11 +3,16 @@ ''' import sys import os +import binascii + +python2=False +if sys.version_info[0] < 3: + python2=True try: from binaryninja import * except: - print "[+] Not running in BinaryNinja" + print ("[+] Not running in BinaryNinja") class callConv(object): def __init__(self, name, arch): @@ -25,7 +30,7 @@ def __init__(self, name, arch): self.platform = '' def gen_arg_number(self, argno, indent=1): - print "X64" + print ("X64") if self.platform == "win": return self.msft(argno, indent) return self.systemV(argno, indent) @@ -83,7 +88,7 @@ def genPointer(self, arg, regs, indent): def gen_arg_number(self, arg, indent): regs = ["UC_ARM_REG_R0", "UC_ARM_REG_R1", "UC_ARM_REG_R2", "UC_ARM_REG_R3"] - if argno < len(regs): + if arg.num < len(regs): if arg.pointerDepth == 0 or arg.pointerDepth > 1: return ' ' * (indent *4 ) + "self.mu.reg_write(%s, arg_%x)\n" % (regs[arg.num], arg.num) return self.genPointer(arg, regs, indent) @@ -159,14 +164,14 @@ def add_mmap(self, addr, len=0x4000): def add_data(self, data, addr): if (self.data_saved(addr)): - print "[Warning] Trying to map data twice!" + print ("[Warning] Trying to map data twice!") return self.data.append((addr, data)) self.saved_ranges.append((addr, addr + len(data))) def add_code(self, cSlice): if (self.data_saved(cSlice.address)): - print "[Warning] Trying to map data twice" + print ("[Warning] Trying to map data twice") self.code.append(cSlice) self.saved_ranges.append((cSlice.address, cSlice.address + len(cSlice.code_bytes))) @@ -199,16 +204,26 @@ def generate_mmap(self, indent = 1): return out def generate_data_vars(self, indent = 1): + global python2 out = '' for i in range(0, len(self.data)): - out += ' ' * (indent * 4) + "self.data_%s = '%s'.decode('hex') \n" % (str(i), self.data[i][1].encode('hex')) + if python2==True: + cc=binascii.hexlify(self.data[i][1]) + else: + cc=binascii.hexlify(self.data[i][1]).decode('utf-8') + out += ' ' * (indent * 4) + "self.data_%s = binascii.unhexlify('%s') \n" % (str(i), cc) return out def generate_code_vars(self, indent = 1): + global python2 out = '' i = 0 for cSlice in self.code: - out += ' ' * (indent * 4) + "self.code_%s = '%s'.decode('hex') \n" % (str(i), cSlice.code_bytes.encode('hex')) + if python2==True: + cc=binascii.hexlify(cSlice.code_bytes) + else: + cc=binascii.hexlify(cSlice.code_bytes).decode('utf-8') + out += ' ' * (indent * 4) + "self.code_%s = binascii.unhexlify('%s') \n" % (str(i), cc) i += 1 return out @@ -227,7 +242,7 @@ def generate_stack_initialization(self, indent = 1): out = ' ' * (indent * 4) + "self.mu.reg_write(UC_ARM_REG_SP, 0x7fffff00)\n" ## TODO Add support for other architectures supported by Unicorn and Binja else: - print "[ripr] Error, Unsupported Architecture" + print ("[ripr] Error, Unsupported Architecture") return out @@ -288,7 +303,7 @@ def generate_return_guard_marker(self, indent=1): elif self.arch == 'arm': out += ' ' * (indent *4) + "self.mu.reg_write(UC_ARM_REG_LR, 0x4)\n" else: - print "Unsupported Arch" + print ("Unsupported Arch") return out def generate_restore_exec(self, indent=1): @@ -307,7 +322,7 @@ def generate_restore_exec(self, indent=1): out += ' ' * (indent * 4) + "self._start_unicorn(retAddr)\n" pass else: - print "Unsupported Arch" + print ("Unsupported Arch") return out @@ -320,7 +335,7 @@ def generate_hook_lookup(self, indent=1): retAddr = ' ' * (indent * 4) + "retAddr = self.mu.reg_read(UC_ARM_REG_LR)\n" else: - print "Unsupported Architecture" + print ("Unsupported Architecture") retAddr = ' ' * (indent * 4) + "retAddr = 0\n" chk_hookdict = ' ' * (indent * 4) + "if retAddr in self.hookdict.keys():\n" @@ -343,7 +358,7 @@ def generate_return_guard(self, indent=1): elif (self.arch == 'arm'): out += ' ' * (indent * 4) + "if self.mu.reg_read(UC_ARM_REG_PC) == 4:\n" else: - print "[ripr] Unsupported Arch..." + print ("[ripr] Unsupported Arch...") # Return if PC has landed on the marker value out += ' ' * ((indent + 1) * 4) + "return\n" @@ -371,7 +386,7 @@ def generate_return_conv(self, indent=1): elif self.arch == 'arm': return ' ' * (indent * 4) + "return self.mu.reg_read(UC_ARM_REG_R0)\n" else: - print '[ripr] Unsupported Arch' + print ('[ripr] Unsupported Arch') def generate_run_with_args(self, indent=1): decl = ' ' * 4 + "def run(self" @@ -486,6 +501,7 @@ def generate_unset_var_comments(self): return out def generate_class(self): + global python2 ''' Wrap this chunk of code into a python class ''' @@ -495,8 +511,12 @@ def generate_class(self): if "unset_vars" in self.conPass.keys(): comments += self.generate_unset_var_comments() # Static Strings - defn = "class %s(object):\n" % (self.name) - imp = "from unicorn import *\n" + self.imp_consts() + "import struct\n" + if python2: + name=self.name + else: + name=self.name.decode('utf-8') + defn = "class %s(object):\n" % name + imp = "from unicorn import *\n" + self.imp_consts() + "import struct\nimport binascii\n" init = ' ' * 4 + "def __init__(self):\n" run = ' ' * 4 + "def run(self):\n" @@ -522,6 +542,14 @@ def generate_class(self): writes = self.generate_mem_writes(indent = 2) + "\n" start_unicorn = self.generate_start_unicorn_func() - + + argf="" + for i in range(0,len(self.conPass['args'])): + argf+="0," + argf=argf[:-1] + # Put the pieces together - self.final = comments + imp + defn + init + emuinit + codevars + datavars + mmaps + writes + hookdict + hooks + start_unicorn + runfns +"\n" + self.final = comments + imp + defn + init + emuinit + codevars + datavars + mmaps + writes \ + + hookdict + hooks + start_unicorn + runfns + "\n" + ("x = %s()" % name) \ + +"\n"+"print (x.run("+argf+"))\n" + diff --git a/conScan.py b/conScan.py index 9bb07a7..0444b5f 100644 --- a/conScan.py +++ b/conScan.py @@ -1,4 +1,5 @@ from binaryninja import * +from .analysis_engine import * class ilVar(object): def __hash__(self): @@ -33,7 +34,6 @@ def __init__(self, _type, num): self.type = _type self.num = num self.pointerDepth = str(self.type).count("*") -from analysis_engine import * class convenienceScanner(object): def __init__(self, engine): @@ -41,7 +41,7 @@ def __init__(self, engine): def argIdent(self, addr, isFunc): if (isinstance(self.engine, radare2_engine)): - print "Unsupported!" + print ("Unsupported!") return None fobj = self.engine.bv.get_functions_containing(addr) if len(fobj) > 1: @@ -58,7 +58,7 @@ def argIdent(self, addr, isFunc): def uninit_vars(self, bbs): for bb in bbs: - mlb = self.engine.find_mlil_block_from_addr(bb.keys()[0]) + mlb = self.engine.find_mlil_block_from_addr(list(bb.keys())[0]) if mlb == None: continue set_vars = [] @@ -72,6 +72,6 @@ def uninit_vars(self, bbs): unset_vars = set(unset_vars) for uVar in unset_vars: - self.engine.highlight_instr(bb.keys()[0], uVar.mil.address, "orange") + self.engine.highlight_instr(list(bb.keys())[0], uVar.mil.address, "orange") return unset_vars diff --git a/defunct/.widgets.py.swp b/defunct/.widgets.py.swp deleted file mode 100644 index b03c29590d0931125dd6cb082eda6d7279d80b0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeHN&yVCr6|M~=At3|=7mz@D42Ycx9nb6n*=V%8!rSecvFNsY+@2j)QPgs`-QBfu z+bvghX6AsD5W)co5F8cfK4e$mq0d|2M z;6>ouz|+7}z=w}RFYq?-Q(z1{4*ch{Li`hW7kC?Z3-}4J4_pHt1Fit?f>Y-8cg$^l z)eL+x2B^AJl8&tYFfS4-isLZP(@v7j?V*-=ww^`Pq(SBj89Z(XUV|@Y=%OU6Y0x?O zZDCgMs&i(d!J+x!X1!deqk#ugrGc~Jbc^tvj*hZnvc57upC!dQzUa9hm5Lh1!iLrF z%S`Hie?@jwChk@~e5H(i zVaW`$+#rc7Lt>alO3?~Y>-)M~rL6t(X5+#(L4~qZqMP%gNHXnaadK>S%Icdb$pijq zy$ojKG%w&W!V057wXc=Hih&~0^CGL#ft4BOnMZyw%is^#q94wR9Gk51K^8yqb!PE2 zl3D)hvKqcRk!?n)&GDUT(oO3G0+$∾ zG!f?GF(i2XC7h%3`csr1m*M}Z{LJQY?P7z+@97eon)}x#IDYX|G{B!+& zRE*VW?w)JJi}{O<*j{vDJ&82URidbSvaf%@y*AC^S5h`ih;esW_GV=mE?YPE;VDIm z@Ww!kL?=g|PE$y8cH?tXLpR!g*_1hs2`E-<9g?WV;AFX}eH1CV@ zZMLsh<`%EGWnKuu@1LH(6CT=uV+sEtB!7qefLxE7zUMj3(Dgk!bi+Lg_Z(V?vIsvP z^c=qRaUW32vD>b<+vCL;5N+4K!@ z$KG|!D&wTsaYN7P^-4MF+Ck_x588G>-GiX(BOKdnk>`7^w-dm)({a4e;)pz-oV$3S z-k#lVn*iNTx9vKu2Bw?sgBJUG2Ml_C2yZ%W=(G@);R*BxwimkW$qsd#pt*;~1wDeP zTvF@ys2liq-4>Rw*Xk+A3=YChm| zk(N$hZx3DL-U~oui2?o3JMle zYJy~@0iR2T={&}H0=)`DNJYH9rJ7&_86W4Qa#ZGgz{eRL;gixR=R!aYz+@Ji)~vRd zj-Pd~QF@V=K4|`-I1Z>fn&8~V`WC^U=Lw9XU>hcw22Jx=j!y71DaII_%?BxbYfvl| zJ}w{3H3s1vnoW}NDnz`=%q55f0i98PB=sbpYaB9otvUZTs_;3K#dIpCQk&LgmK#Eq zFtme7Rq<}3!pSQiO0<)ho5CE8y;?YuDYkH(&95zjTia+DMw2KN-P8Sm&Ph@vgA*DT z*y|+5alA;(p>#Mw|3grqB+5=I<|rR%^kVinG9(GnT<-h&aD#4M-@5j~ZMy0CU1}w- zCTTun#}u{O&D;3x*F|!dbP3TaKaQuHIKA*!@;4)WySlYxew&_0EnL)M5t}fr=YO_< BMOy#> diff --git a/dependency.py b/dependency.py index 25ea443..956c3c8 100644 --- a/dependency.py +++ b/dependency.py @@ -4,7 +4,7 @@ properly. ''' -import analysis_engine as ae +from .analysis_engine import aengine as ae from binaryninja import * class ImportedCall(object): @@ -71,7 +71,7 @@ def branchScan(self, address, isFunc=True): Function is responsible for mapping calls and jumps that are outside the current selection's bounds, if possible. ''' - print "[ripr] Inside branchScan" + print ("[ripr] Inside branchScan") def callCallback(dest, instr_addr): if type(dest) != int: try: @@ -79,17 +79,17 @@ def callCallback(dest, instr_addr): except: return if (dest in self.imports): - print "[ripr] Found imported Call target..." + print ("[ripr] Found imported Call target...") self._mark_imported_call(address, instr_addr, dest) elif (self.codeobj.data_saved(dest) == False): - print "[ripr] Found LLIL CALL instruction" + print ("[ripr] Found LLIL CALL instruction") self._mark_additional_branch(address, instr_addr, dest, "call") else: - print "[ripr] Target address already mapped" + print ("[ripr] Target address already mapped") def jumpCallback(dest, instr_addr): - print "[ripr] JUMP TARGET: %s" % (dest) + print ("[ripr] JUMP TARGET: %s" % (dest)) if isFunc: self.engine.branches_from_func(address, callCallback, jumpCallback) @@ -109,7 +109,7 @@ def _find_stringRefs(self, address): for stringStart,stringLength in self.engine.get_strings(): for refAddress in self.engine.get_refs_to(stringStart): # Ignored the length if (self.engine.function_contains_addr(address, refAddress)): - print "[ripr] Found string reference: 0x%x" % (refAddress) + print ("[ripr] Found string reference: 0x%x" % (refAddress)) self._mark_identified_data(address, refAddress) dref = riprDataRef(stringStart, stringLength, 'str') self.dataRefs.append(dref) @@ -124,7 +124,7 @@ def _find_symbolRefs(self, address): for symStart in symbols: for refAddress in self.engine.get_refs_to(symStart): if self.engine.function_contains_addr(address, refAddress): - print "[ripr] Found Symbol Reference: 0x%x references 0x%x" % (refAddress, symStart) + print ("[ripr] Found Symbol Reference: 0x%x references 0x%x" % (refAddress, symStart)) self._mark_identified_data(address, refAddress) dref = riprDataRef(symStart, -1, 'sym') self.dataRefs.append(dref) @@ -142,7 +142,7 @@ def dataScan(self, address): Function is responsible for finding data the target code needs in order to run correctly. ''' - print "[ripr] Inside dataScan" + print ("[ripr] Inside dataScan") ret = [] # Find the low-hanging fruit @@ -151,7 +151,7 @@ def dataScan(self, address): # Iterate over all instructions for potential pointers for target, instrAddr in self.engine.scan_potential_pointers(address): if self.engine.is_plausible_pointer(target): - print "Found Potential Pointer: %s instaddr %s" % (hex(target), hex(instrAddr)) + print ("Found Potential Pointer: %s instaddr %s" % (hex(target), hex(instrAddr))) self._mark_identified_data(address, instrAddr) dref = riprDataRef(target, -1, 'ptr') self.dataRefs.append(dref) diff --git a/gui.py b/gui.py index bf88ce2..1c3ebbd 100644 --- a/gui.py +++ b/gui.py @@ -3,12 +3,12 @@ ''' import sys if (sys.platform == 'win32'): - sys.path.append("C:\\Python27\\lib\\site-packages") + sys.path.append("C:\\Python37\\lib\\site-packages") try: from PyQt5 import QtWidgets, QtGui, QtCore from PyQt5.QtCore import Qt - from defunct.widgets import BinjaWidget - import defunct.widgets + from .defunct.widgets import BinjaWidget + from .defunct.widgets import * qtAvailable = True except: qtAvailable = False @@ -42,7 +42,7 @@ def _get_selected_codeobj(self): # Find the Class name from the index name = self.item(item.row(), item.column()).text() # Open the Editor - print "Selected Codeobj: %s" % name + print ("Selected Codeobj: %s" % name) return self.emuchunks[name] def contextMenuEvent(self, event): @@ -183,7 +183,10 @@ def yes_no_box(self, msg): if choice == enums.MessageBoxButtonResult.YesButton: return True return False - + + def msgBox(self, msg): + interaction.show_message_box("Binary Ninja - ripr", msg, enums.MessageBoxButtonSet.OKButtonSet) + def text_input_box(self,msg): text = interaction.get_text_line_input(msg, "Binary Ninja - ripr") return text diff --git a/packager.py b/packager.py index 5a6e5fa..b781af8 100644 --- a/packager.py +++ b/packager.py @@ -1,7 +1,7 @@ -from codegen import * -import analysis_engine as ae -import dependency as dep -import conScan as con +from .codegen import * +from .analysis_engine import aengine as ae +from .dependency import depScanner +from .conScan import convenienceScanner ### Global for listing all chunks of code for which we have tried to create a python wrapper. emuchunks = {} @@ -40,7 +40,7 @@ def convenience_passes(self): This function is a wrapper for determining which convenience features can be enabled during code generation. ''' - c = con.convenienceScanner(self.engine) + c = convenienceScanner(self.engine) if (self.isFunc == True and self.codeobj.arch in ['x64', 'x86', 'arm']): self.codeobj.conPass['ret'] = True @@ -66,7 +66,7 @@ def minimal_package_function(self, address=None): # Add each contiguous chunk of code to codeobj and make sure # it will be mapped. - for startAddr in localCode.keys(): + for startAddr in list(localCode.keys()): self.codeobj.add_mmap(startAddr) self.targetCode.append(localCode) @@ -132,7 +132,7 @@ def generate_bb_code(self): if not self.codeobj.name: return # Set starting address to first basic block selected - self.codeobj.startaddr = bbChunks[0].keys()[0] + self.codeobj.startaddr = list(bbChunks[0].keys())[0] self.targetCode = bbChunks @@ -154,7 +154,7 @@ def generate_bb_code(self): def cleanup_basic_blocks(self): global bbChunks for bb in bbChunks: - self.engine.clean_gathered_basic_block(bb.keys()[0]) + self.engine.clean_gathered_basic_block(list(bb.keys())[0]) def package_region(self): @@ -176,14 +176,14 @@ def _find_code_unit(self, faddr): def _nop_impFunc(self, impCalls): for impCall in impCalls: - print "[ripr] Nopping out Imported Call: 0x%x" % (impCall.address) + print ("[ripr] Nopping out Imported Call: 0x%x" % (impCall.address)) cSlice = self._find_code_unit(impCall.address) codeLen = len(cSlice.code_bytes) nop = self.engine.get_nop_opcode() if (impCall.inst_len % len(nop) != 0): - print "[ripr] Cannot NOP out instruction..." + print ("[ripr] Cannot NOP out instruction...") return # Create string of NOP opcodes and calculate where to place it @@ -200,15 +200,15 @@ def update_codeobj(self): # in the case of others being automatically mapped as dependencies localCode = self.targetCode[0] - print self.targetCode - self.codeobj.codelen = sum([len(localCode[x].code_bytes) for x in localCode.keys()]) + print (self.targetCode) + self.codeobj.codelen = sum([len(localCode[x].code_bytes) for x in list(localCode.keys())]) for found_code in self.targetCode: for addr in found_code: self.codeobj.add_code(found_code[addr]) def resolve_imported_calls(self, resolv): - print "[ripr] Selection includes calls to imported Functions!" + print ("[ripr] Selection includes calls to imported Functions!") if self.impCallStrategy == None: self.impCallStrategy = self.ui.impCallsOptions() @@ -236,7 +236,7 @@ def map_dependent_sections(self, dataRefs): ''' Map any sections the target code touches. ''' - print "Mapping Sections" + print ("Mapping Sections") pagesize = self.engine.get_page_size() secs = [] for ref in dataRefs: @@ -257,10 +257,10 @@ def resolve_data_dependencies(self, dataRefs): This function handles finding data that needs to get mapped. ''' if (self.dataStrategy == None): - if (self.ui.yes_no_box("Use Section-Marking Mode for data dependencies (default; yes)")): - self.dataStrategy = "section" - else: + if (self.ui.yes_no_box("Use Page-Marking Mode for data dependencies (default; yes)")): self.dataStrategy = "page" + else: + self.dataStrategy = "section" if (self.dataStrategy == "section"): self.map_dependent_sections(dataRefs) @@ -269,7 +269,7 @@ def resolve_data_dependencies(self, dataRefs): def resolve_codeRefs(self, coderefs): for ref in coderefs: - print "Found CodeRef: %x::%s" % (ref.address, ref.type) + print ("Found CodeRef: %x::%s" % (ref.address, ref.type)) if (ref.type == 'call'): self.minimal_package_function(address=ref.address) self.resolve_dependencies(address=ref.address, isFunc=True) @@ -278,14 +278,14 @@ def resolve_dependencies(self, address=None, isFunc=None): ''' This method is a high-level wrapper for finding data our target code depends on. ''' - resolv = dep.depScanner(self.engine, self.codeobj) + resolv = depScanner(self.engine, self.codeobj) if (address == None): address = self.address if (isFunc == None): isFunc = self.isFunc - print "Resolving Dependencies for %x" % address + print ("Resolving Dependencies for %x" % address) if isFunc: coderefs = resolv.branchScan(address, self.isFunc) datarefs = resolv.dataScan(address) @@ -293,22 +293,22 @@ def resolve_dependencies(self, address=None, isFunc=None): datarefs = resolv.dataScan(address) coderefs = [] for bb in bbChunks: - coderefs += resolv.branchScan(bb.keys()[0], self.isFunc) + coderefs += resolv.branchScan(list(bb.keys())[0], self.isFunc) if (resolv.impCalls != []): self.resolve_imported_calls(resolv) if (coderefs != []): if (self.ui.yes_no_box("Target code may depend on outside code, attempt to map automatically?") == True): - print "[ripr] Performing analysis on code dependencies..." + print ("[ripr] Performing analysis on code dependencies...") self.resolve_codeRefs(coderefs) else: pass if (resolv.dataRefs != []): # Try to map these automatically - print "[ripr] Found these potential Data References" + print ("[ripr] Found these potential Data References") for ref in resolv.dataRefs: - print "Data Referenced: 0x%x" % (ref.address) + print ("Data Referenced: 0x%x" % (ref.address)) self.resolve_data_dependencies(resolv.dataRefs) pass diff --git a/r2pipe_run.py b/r2pipe_run.py index 762e3e0..376e64a 100644 --- a/r2pipe_run.py +++ b/r2pipe_run.py @@ -1,13 +1,13 @@ import sys # ripr imports -from analysis_engine import * -from codegen import * -from packager import * -import cli_ui +from .analysis_engine import * +from .codegen import * +from .packager import * +from .cli_ui import * def packageFunction(addr): - print "[ripr] Packaging function {}".format(hex(addr)) + print ("[ripr] Packaging function {}".format(hex(addr))) engine = get_engine(addr) ui = cli_ui.cli_ui() pkg = Packager(isFunc=True, address=addr, engine=engine, ui=ui) diff --git a/sample/impCall/ripr_arm_impCall.py b/sample/impCall/ripr_arm_impCall.py index 34e6021..e250473 100644 --- a/sample/impCall/ripr_arm_impCall.py +++ b/sample/impCall/ripr_arm_impCall.py @@ -17,15 +17,15 @@ def __init__(self): self.hookdict = {66688L: 'hook_puts', 66696L: 'hook_puts', 66704L: 'hook_malloc'} def hook_puts(self): - print "===========In Puts==========" + print ("===========In Puts==========") arg = self.mu.reg_read(UC_ARM_REG_R0) mem = self.mu.mem_read(arg, 0x200) - print "Puts would have Printed: %s" % (mem.split("\x00")[0]) - print "===========Leaving Puts==========" + print ("Puts would have Printed: %s" % (mem.split("\x00")[0])) + print ("===========Leaving Puts==========") def hook_malloc(self): - print "===========In Malloc==========" - print "===========Leaving Malloc===========" + print ("===========In Malloc==========") + print ("===========Leaving Malloc===========") def _start_unicorn(self, startaddr): try: @@ -46,4 +46,4 @@ def run(self): return self.mu.reg_read(UC_ARM_REG_R0) x = arm_test() -print x.run() +print (x.run()) diff --git a/sample/impCall/ripr_x64_impCall.py b/sample/impCall/ripr_x64_impCall.py index ead6eba..685208b 100644 --- a/sample/impCall/ripr_x64_impCall.py +++ b/sample/impCall/ripr_x64_impCall.py @@ -18,15 +18,15 @@ def __init__(self): self.hookdict = {4195721L: 'hook_puts', 4195731L: 'hook_malloc', 4195711L: 'hook_puts'} # Do whatever we want in hooked functions def hook_puts(self): - print "===========In Puts==========" + print ("===========In Puts==========") arg = self.mu.reg_read(UC_X86_REG_RDI) mem = self.mu.mem_read(arg, 0x200) - print "Puts would have Printed: %s" % (mem.split("\x00")[0]) - print "===========Leaving Puts==========" + print ("Puts would have Printed: %s" % (mem.split("\x00")[0])) + print ("===========Leaving Puts==========") def hook_malloc(self): - print "===========In Malloc==========" - print "===========Leaving Malloc===========" + print ("===========In Malloc==========") + print ("===========Leaving Malloc===========") def _start_unicorn(self, startaddr): @@ -49,4 +49,4 @@ def run(self): return self.mu.reg_read(UC_X86_REG_RAX) x = x64_test() -print x.run() +print (x.run()) diff --git a/sample/impCall/ripr_x86_impCall.py b/sample/impCall/ripr_x86_impCall.py index 18d3218..07138be 100644 --- a/sample/impCall/ripr_x86_impCall.py +++ b/sample/impCall/ripr_x86_impCall.py @@ -17,17 +17,17 @@ def __init__(self): self.hookdict = {134513753L: 'hook_puts', 134513782L: 'hook_malloc', 134513769L: 'hook_puts'} def hook_puts(self): - print "===========In Puts==========" + print ("===========In Puts==========") arg = self.mu.reg_read(UC_X86_REG_ESP) arg2 = self.mu.mem_read(arg+4, 0x4) arg2 = struct.unpack(" " % (sys.argv[0]) + print ("Usage: %s <key> <plaintext>" % (sys.argv[0])) exit() key=sys.argv[1] @@ -137,4 +137,4 @@ def run(self, arg_0, arg_1): ksa=KSA() S=str(ksa.run(key,S)) prga=PRGA() -print str(prga.run(S,plain)).encode('hex') +print (str(prga.run(S,plain)).encode('hex')) diff --git a/sample/rc4/tester.py b/sample/rc4/tester.py index 155195e..4258014 100755 --- a/sample/rc4/tester.py +++ b/sample/rc4/tester.py @@ -12,7 +12,7 @@ if args.seed != None: random.seed(args.seed) -print "Running %d testcases..." % (args.num) +print ("Running %d testcases..." % (args.num)) for i in xrange(0,args.num): key_len = random.randint(1,16) @@ -22,7 +22,7 @@ ripr = subprocess.check_output(["python", "myrc4.py", key, plain]).strip().upper() orig = subprocess.check_output(["./a.out", key, plain]).strip().upper() if ripr != orig: - print "FAIL:\n'%s' vs. '%s'" % (orig, ripr) - print "Key: %s\nPlain:\n'%s'" % (key, plain) + print ("FAIL:\n'%s' vs. '%s'" % (orig, ripr)) + print ("Key: %s\nPlain:\n'%s'" % (key, plain)) exit() print "Everything is OK!" From ed063e38e5f74ed2793bb9577c95a3ec98a09f43 Mon Sep 17 00:00:00 2001 From: Bjoern Kerler <info@revskills.de> Date: Wed, 22 Aug 2018 23:57:40 +0200 Subject: [PATCH 2/5] Add pull requests #15 and #16 --- README.md | 5 +++++ codegen.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++++--- conScan.py | 10 +++++++++- dependency.py | 11 ++++++++++- packager.py | 2 +- 5 files changed, 75 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index cab3e67..5a3c671 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Installation on Windows typically requires installing PyQt5. --- #### Packaging a Function +##### Binary Ninja From within Binary Ninja, right click anywhere inside of a function and select `[ripr] Package Function`. <img src="https://puu.sh/thLAo/491ac39e58.PNG" width="600"> @@ -43,6 +44,10 @@ After packaging, a table will appear listing all of the "packages" you have crea <img src="https://puu.sh/tnz8C/d0f5141f43.PNG" width="600"> +##### Radare2 +If you've followed step 3 in the installation instructions, run `.(ripr 0x1234)` (with 0x1234 replaced by the address of the function). +Otherwise, you can manually invoke ripr with `#!pipe python /absolute/path/to/ripr/r2pipe_run.py 0x1234`. + #### Packaging Specific Basic Blocks You can also choose to only package specific basic blocks rather than the entire function. diff --git a/codegen.py b/codegen.py index f0cf129..1e56ca8 100644 --- a/codegen.py +++ b/codegen.py @@ -22,13 +22,24 @@ def __init__(self, name, arch): def gen_arg_number(self, argno): pass + def genPointer(self, arg, regs, indent): + pass + + def dumpContext(self, indent): + pass + class x64callConv(callConv): # TODO Stack based arguments def __init__(self, name, arch): self.name = name self.arch = arch self.platform = '' - + self.regs = ["UC_X86_REG_RAX", "UC_X86_REG_RBP", "UC_X86_REG_RBX", "UC_X86_REG_RCX",\ + "UC_X86_REG_RDI", "UC_X86_REG_RDX", "UC_X86_REG_RSI", "UC_X86_REG_RSP",\ + "UC_X86_REG_RIP", "UC_X86_REG_R8", "UC_X86_REG_R9", "UC_X86_REG_R10",\ + "UC_X86_REG_R11", "UC_X86_REG_R12", "UC_X86_REG_R13", "UC_X86_REG_R14",\ + "UC_X86_REG_R15"] + def gen_arg_number(self, argno, indent=1): print ("X64") if self.platform == "win": @@ -57,10 +68,19 @@ def systemV(self, arg, indent): return ' ' * (indent*4) + "self.mu.reg_write(%s, arg_%x)\n" % (regs[arg.num], arg.num) return self.genPointer(arg, regs, indent) + def dumpContext(self, indent): + ret = ' ' * (indent * 4) + ("print ('[!] Exception occured - Emulator state (x64):')\n") + for r in self.regs: + ret += ' ' * (indent * 4) + ("print (\"%s : %%016X\" %% (self.mu.reg_read(%s)))\n" % (r,r)) + return ret + class x86callConv(callConv): def __init__(self, name, arch): self.name = name self.arch = arch + self.regs = ["UC_X86_REG_EAX", "UC_X86_REG_EBP", "UC_X86_REG_EBX", "UC_X86_REG_ECX",\ + "UC_X86_REG_EDI", "UC_X86_REG_EDX", "UC_X86_REG_ESI", "UC_X86_REG_ESP",\ + "UC_X86_REG_EIP"] def genPointer(self, arg, indent): ret = ' ' * (indent * 4) + "argAddr_%x = (%d * 0x1000)\n" % (arg.num, arg.num + 1) @@ -75,10 +95,20 @@ def gen_arg_number(self, arg, indent): return ' ' * (indent * 4) + "self.mu.mem_write(self.mu.reg_read(UC_X86_REG_ESP) + %d, struct.pack('<i', arg_%x))\n" % ( (arg.num * 4) + 4, arg.num) return self.genPointer(arg, indent) + def dumpContext(self, indent): + ret = ' ' * (indent * 4) + ("print ('[!] Exception occured - Emulator state (x86):')\n") + for r in self.regs: + ret += ' ' * (indent * 4) + ("print (\"%s : %%08X\" %% (self.mu.reg_read(%s)))\n" % (r,r)) + return ret + class armcallConv(callConv): def __init__(self, name, arch): self.name = name self.arch = arch + self.regs = ["UC_ARM_REG_R0", "UC_ARM_REG_R1", "UC_ARM_REG_R2", "UC_ARM_REG_R3",\ + "UC_ARM_REG_R4", "UC_ARM_REG_R5", "UC_ARM_REG_R6", "UC_ARM_REG_R7",\ + "UC_ARM_REG_R8", "UC_ARM_REG_R9", "UC_ARM_REG_R10", "UC_ARM_REG_R11",\ + "UC_ARM_REG_R12", "UC_ARM_REG_R13", "UC_ARM_REG_R14", "UC_ARM_REG_R15"] def genPointer(self, arg, regs, indent): ret = ' ' * (indent * 4) + "argAddr_%x = (%d * 0x1000)\n" % (arg.num, arg.num+1) @@ -92,7 +122,12 @@ def gen_arg_number(self, arg, indent): if arg.pointerDepth == 0 or arg.pointerDepth > 1: return ' ' * (indent *4 ) + "self.mu.reg_write(%s, arg_%x)\n" % (regs[arg.num], arg.num) return self.genPointer(arg, regs, indent) - + + def dumpContext(self, indent): + ret = ' ' * (indent * 4) + ("print ('[!] Exception occured - Emulator state (arm):')\n") + for r in self.regs: + ret += ' ' * (indent * 4) + ("print (\"%s : %%X\" %% (self.mu.reg_read(%s)))\n" % (r,r)) + return ret class codeSlice(object): ''' @@ -155,6 +190,15 @@ def __init__(self, name, isFunc=True): self.isFunc = isFunc + def setArch(self,a): + self.arch=a + if self.arch == 'x64': + self.callConv = x64callConv("linux", "x64") + if self.arch == 'x86': + self.callConv = x86callConv("linux", "x86") + if self.arch == 'arm': + self.callConv =armcallConv("linux", "arm") + def data_saved(self, addr): return any(lowaddr <= addr <= highaddr for (lowaddr, highaddr) in self.saved_ranges) @@ -369,6 +413,8 @@ def generate_return_guard(self, indent=1): # Raise original exception if PC is not equal to the appropriate marker value or imported call marker out += ' ' * (indent * 4) + "else:\n" + if self.callConv is not None: + out += self.callConv.dumpContext(indent+1) out += ' ' * ((indent + 1) * 4) + "raise e" return out + "\n" @@ -465,7 +511,8 @@ def generate_default_hookFunc(self, name, indent=1): The default python hook for imported calls should do nothing. ''' out = ' ' * (indent * 4) + """def hook_%s(self): - pass\n""" % name + print ("[!] %s hook not implemented!") + pass\n""" % (name, name) return out def _build_impCall_hook_dict(self, indent=1): diff --git a/conScan.py b/conScan.py index 0444b5f..be2535c 100644 --- a/conScan.py +++ b/conScan.py @@ -1,4 +1,12 @@ -from binaryninja import * +try: + from binaryninja import * +except: + print ("[!!] Not running in Binary Ninja") +try: + import r2pipe +except: + print ("[!!] Not running in Radare2") + from .analysis_engine import * class ilVar(object): diff --git a/dependency.py b/dependency.py index 956c3c8..7f26b46 100644 --- a/dependency.py +++ b/dependency.py @@ -5,7 +5,16 @@ ''' from .analysis_engine import aengine as ae -from binaryninja import * +# Try to import stuff. +try: + from binaryninja import * +except: + print ("[!!] Not running in Binary Ninja") +try: + import r2pipe +except: + print ("[!!] Not running in Radare2") + class ImportedCall(object): ''' diff --git a/packager.py b/packager.py index b781af8..2b110f1 100644 --- a/packager.py +++ b/packager.py @@ -26,7 +26,7 @@ def __init__(self, isFunc, address, engine, ui = None, length=None): self.codeobj = genwrapper('', isFunc) self.arch = self.engine.get_arch() - self.codeobj.arch = self.arch + self.codeobj.setArch(self.arch) self.impCallStrategy = None self.dataStrategy = None From 95506e0e03ff797ba32c6b0541a4d7bd39fc1a27 Mon Sep 17 00:00:00 2001 From: Bjoern Kerler <info@revskills.de> Date: Thu, 23 Aug 2018 19:14:23 +0200 Subject: [PATCH 3/5] Add arm64 support and fix section generation, added new hooking mechanism --- __init__.py | 6 ++- analysis_engine.py | 11 ++++- codegen.py | 106 +++++++++++++++++++++++++++++++++++---------- gui.py | 5 ++- packager.py | 9 ++-- 5 files changed, 108 insertions(+), 29 deletions(-) diff --git a/__init__.py b/__init__.py index 0942a79..2beb12e 100644 --- a/__init__.py +++ b/__init__.py @@ -1,6 +1,10 @@ import sys + if (sys.platform == 'win32'): - sys.path.append("C:\\Python37\\lib\\site-packages\\PyQt5") + if sys.version_info[0] >= 3: + sys.path.append("C:\\Python37\\lib\\site-packages\\PyQt5") + else: + sys.path.append("C:\\Python27\\lib\\site-packages\\PyQt5") try: from binaryninja import * diff --git a/analysis_engine.py b/analysis_engine.py index e542a65..2c2e071 100644 --- a/analysis_engine.py +++ b/analysis_engine.py @@ -54,6 +54,7 @@ def get_arch(self): 'x86' 'x64' 'arm' + 'arm64' 'mips' ''' pass @@ -167,6 +168,8 @@ def get_arch(self): return 'x64' elif (self.bv.arch.name == 'armv7'): return 'arm' + elif (self.bv.arch.name == 'aarch64'): + return 'arm64' def mark_gathered_basic_block(self, address): fobj = self.bv.get_functions_containing(address)[0] @@ -442,7 +445,13 @@ def get_arch(self): elif arch == "x86" and bits == 64: return 'x64' else: - raise (NotImplementedError, "Only tested witn x86 & x86_64") + raise (NotImplementedError, "Only tested with x86 & x86_64") + ''' + elif arch == "arm" and bits == 32: + return 'arm' + elif arch == "arm" and bits == 64: + return 'arm64' + ''' def get_function_bytes(self, address=None, name=None): if (address != None): diff --git a/codegen.py b/codegen.py index 1e56ca8..04d4e4f 100644 --- a/codegen.py +++ b/codegen.py @@ -129,6 +129,38 @@ def dumpContext(self, indent): ret += ' ' * (indent * 4) + ("print (\"%s : %%X\" %% (self.mu.reg_read(%s)))\n" % (r,r)) return ret +class arm64callConv(callConv): + def __init__(self, name, arch): + self.name = name + self.arch = arch + self.regs = ["UC_ARM64_REG_X0", "UC_ARM64_REG_X1", "UC_ARM64_REG_X2", "UC_ARM64_REG_X3",\ + "UC_ARM64_REG_X4", "UC_ARM64_REG_X5", "UC_ARM64_REG_X6", "UC_ARM64_REG_X7",\ + "UC_ARM64_REG_X8", "UC_ARM64_REG_X9", "UC_ARM64_REG_X10", "UC_ARM64_REG_X11",\ + "UC_ARM64_REG_X12", "UC_ARM64_REG_X13", "UC_ARM64_REG_X14", "UC_ARM64_REG_X15",\ + "UC_ARM64_REG_X16", "UC_ARM64_REG_X17", "UC_ARM64_REG_X18", "UC_ARM64_REG_X19",\ + "UC_ARM64_REG_X20", "UC_ARM64_REG_X21", "UC_ARM64_REG_X22", "UC_ARM64_REG_X23",\ + "UC_ARM64_REG_X24", "UC_ARM64_REG_X25", "UC_ARM64_REG_X26", "UC_ARM64_REG_X27",\ + "UC_ARM64_REG_X28", "UC_ARM64_REG_X29", "UC_ARM64_REG_X30"] + + def genPointer(self, arg, regs, indent): + ret = ' ' * (indent * 4) + "argAddr_%x = (%d * 0x1000)\n" % (arg.num, arg.num+1) + ret += ' ' * (indent * 4) + "self.mu.mem_write(argAddr_%x, arg_%x)\n" % (arg.num, arg.num) + ret += ' ' * (indent * 4) + "self.mu.reg_write(%s, argAddr_%x)\n" % (regs[arg.num], arg.num) + return ret + + def gen_arg_number(self, arg, indent): + regs = ["UC_ARM64_REG_X0", "UC_ARM64_REG_X1", "UC_ARM64_REG_X2", "UC_ARM64_REG_X3"] + if arg.num < len(regs): + if arg.pointerDepth == 0 or arg.pointerDepth > 1: + return ' ' * (indent *4 ) + "self.mu.reg_write(%s, arg_%x)\n" % (regs[arg.num], arg.num) + return self.genPointer(arg, regs, indent) + + def dumpContext(self, indent): + ret = ' ' * (indent * 4) + ("print ('[!] Exception occured - Emulator state (arm64):')\n") + for r in self.regs: + ret += ' ' * (indent * 4) + ("print (\"%s : %%X\" %% (self.mu.reg_read(%s)))\n" % (r,r)) + return ret + class codeSlice(object): ''' A container class for a slice of code. @@ -194,10 +226,12 @@ def setArch(self,a): self.arch=a if self.arch == 'x64': self.callConv = x64callConv("linux", "x64") - if self.arch == 'x86': + elif self.arch == 'x86': self.callConv = x86callConv("linux", "x86") - if self.arch == 'arm': + elif self.arch == 'arm': self.callConv =armcallConv("linux", "arm") + elif self.arch == 'arm64': + self.callConv =armcallConv("linux", "arm64") def data_saved(self, addr): return any(lowaddr <= addr <= highaddr for (lowaddr, highaddr) in self.saved_ranges) @@ -284,6 +318,11 @@ def generate_stack_initialization(self, indent = 1): elif self.arch == 'arm': self.add_mmap(0x7ffff000, 1024*1024 * 2) out = ' ' * (indent * 4) + "self.mu.reg_write(UC_ARM_REG_SP, 0x7fffff00)\n" + + elif self.arch == 'arm64': + self.add_mmap(0x7ffff000, 1024*1024 * 2) + out = ' ' * (indent * 4) + "self.mu.reg_write(UC_ARM64_REG_SP, 0x7fffff00)\n" + ## TODO Add support for other architectures supported by Unicorn and Binja else: print ("[ripr] Error, Unsupported Architecture") @@ -333,7 +372,7 @@ def generate_start_unicorn_func(self, indent = 1): ''' decl = ' ' * (indent * 4) + 'def _start_unicorn(self, startaddr):\n' body = self.generate_emustart(indent=2) - return decl+body + return decl+body+"\n" def generate_return_guard_marker(self, indent=1): ''' @@ -346,6 +385,8 @@ def generate_return_guard_marker(self, indent=1): out += ' ' * (indent *4) + "self.mu.mem_write(0x7fffff00, '\\x01\\x00\\x00\\x00')\n" elif self.arch == 'arm': out += ' ' * (indent *4) + "self.mu.reg_write(UC_ARM_REG_LR, 0x4)\n" + elif self.arch == 'arm64': + out += ' ' * (indent *4) + "self.mu.reg_write(UC_ARM64_REG_LR, 0x4)\n" else: print ("Unsupported Arch") return out @@ -365,6 +406,9 @@ def generate_restore_exec(self, indent=1): elif self.arch == 'arm': out += ' ' * (indent * 4) + "self._start_unicorn(retAddr)\n" pass + elif self.arch == 'arm64': + out += ' ' * (indent * 4) + "self._start_unicorn(retAddr)\n" + pass else: print ("Unsupported Arch") @@ -377,6 +421,8 @@ def generate_hook_lookup(self, indent=1): retAddr = ' ' * (indent * 4) + "retAddr = struct.unpack(\"<i\", self.mu.mem_read(self.mu.reg_read(UC_X86_REG_ESP), 4))[0]\n" elif self.arch == 'arm': retAddr = ' ' * (indent * 4) + "retAddr = self.mu.reg_read(UC_ARM_REG_LR)\n" + elif self.arch == 'arm64': + retAddr = ' ' * (indent * 4) + "retAddr = self.mu.reg_read(UC_ARM64_REG_LR)\n" else: print ("Unsupported Architecture") @@ -408,16 +454,16 @@ def generate_return_guard(self, indent=1): out += ' ' * ((indent + 1) * 4) + "return\n" # Check if this crash is the result of an imported Call and execute the hook if applicable - if (self.impCallTargets): - out += self.generate_hook_lookup(indent=indent) + #if (self.impCallTargets): + # out += self.generate_hook_lookup(indent=indent) # Raise original exception if PC is not equal to the appropriate marker value or imported call marker - out += ' ' * (indent * 4) + "else:\n" + #out += ' ' * (indent * 4) + "else:\n" if self.callConv is not None: - out += self.callConv.dumpContext(indent+1) - out += ' ' * ((indent + 1) * 4) + "raise e" + out += self.callConv.dumpContext(indent) + out += ' ' * (indent * 4) + "raise e" - return out + "\n" + return out + "\n\n" def generate_return_conv(self, indent=1): ''' @@ -431,6 +477,8 @@ def generate_return_conv(self, indent=1): return ' ' * (indent * 4) + "return self.mu.reg_read(UC_X86_REG_EAX)\n" elif self.arch == 'arm': return ' ' * (indent * 4) + "return self.mu.reg_read(UC_ARM_REG_R0)\n" + elif self.arch == 'arm64': + return ' ' * (indent * 4) + "return self.mu.reg_read(UC_ARM64_REG_X0)\n" else: print ('[ripr] Unsupported Arch') @@ -441,7 +489,7 @@ def generate_run_with_args(self, indent=1): decl += ", arg_%x" % i decl += '):\n' - return decl + return decl+"\n" def generate_fill_in_args(self, indent=1): decl = '' @@ -451,11 +499,15 @@ def generate_fill_in_args(self, indent=1): cc = x64callConv("linux", "x64") for i in range(0, len(args)): decl += cc.gen_arg_number(args[i], indent) - if self.arch == 'x86': + elif self.arch == 'x86': cc = x86callConv("linux", "x86") for i in range(0, len(args)): decl += cc.gen_arg_number(args[i], indent) - if self.arch == 'arm': + elif self.arch == 'arm': + cc =armcallConv("linux", "arm") + for i in range(0, len(args)): + decl += cc.gen_arg_number(args[i], indent) + elif self.arch == 'arm64': cc =armcallConv("linux", "arm") for i in range(0, len(args)): decl += cc.gen_arg_number(args[i], indent) @@ -485,7 +537,7 @@ def generate_run_functions(self, indent = 1): if (self.conPass['ret'] == True): out += self.generate_return_conv(indent=2) - return out + return out+"\n" def imp_consts(self): ''' @@ -512,7 +564,7 @@ def generate_default_hookFunc(self, name, indent=1): ''' out = ' ' * (indent * 4) + """def hook_%s(self): print ("[!] %s hook not implemented!") - pass\n""" % (name, name) + pass\n\n""" % (name, name) return out def _build_impCall_hook_dict(self, indent=1): @@ -523,14 +575,15 @@ def _build_impCall_hook_dict(self, indent=1): ret = '' out = {} + instlen = {} build_funcs = [] # Get a list of names for hook functions for impCall in self.impCallTargets: if str(impCall.symbol) not in build_funcs: build_funcs.append(str(impCall.symbol)) - out[impCall.address + impCall.inst_len] = "hook_%s" % str(impCall.symbol) - + out[impCall.address] = "['hook_%s',%d]" % (str(impCall.symbol),impCall.inst_len) + # Generate stubs for the hooked functions for func in build_funcs: ret += self.generate_default_hookFunc(func) @@ -538,7 +591,11 @@ def _build_impCall_hook_dict(self, indent=1): return (ret, out) def generate_hookdict(self, hookd, indent=1): - return ' ' * (indent * 4) + "self.hookdict = %s\n" % hookd + hookstr="self.hookdict = {" + for hook in hookd: + hookstr+="0x%x: %s," % (hook,hookd[hook]) + hookstr=hookstr[:-1]+"}\n" + return ' ' * (indent * 4) + hookstr def generate_unset_var_comments(self): out = '# Variables listed below should be filled in: \n' @@ -564,13 +621,14 @@ def generate_class(self): name=self.name.decode('utf-8') defn = "class %s(object):\n" % name imp = "from unicorn import *\n" + self.imp_consts() + "import struct\nimport binascii\n" + init = ' ' * 4 + "def __init__(self):\n" run = ' ' * 4 + "def run(self):\n" # Dyanmic Strings emuinit = self.generate_emuinit(indent = 2) - codevars = self.generate_code_vars(indent = 2) + "\n" - datavars = self.generate_data_vars(indent = 2) + "\n" + codevars = self.generate_code_vars(indent = 2) + datavars = self.generate_data_vars(indent = 2) # Generate run function runfns = self.generate_run_functions(indent=2) @@ -584,9 +642,13 @@ def generate_class(self): hooks = '' hookdict = '' + hookdict=hookdict + hooker=' ' * 8 + "self.mu.hook_add(UC_HOOK_CODE, self.hook_code)\n" + hookcode = ' ' * 4 + "def hook_code(self, mu, address, size, user_data):\n if address in self.hookdict.keys():\n caller=self.hookdict[address][0]\n getattr(self,caller)()\n mu.reg_write(UC_ARM_REG_PC,address+self.hookdict[address][1])\n\n" + # mmaps and writes must be generated at the end - mmaps = self.generate_mmap(indent = 2) + "\n" - writes = self.generate_mem_writes(indent = 2) + "\n" + mmaps = self.generate_mmap(indent = 2) + writes = self.generate_mem_writes(indent = 2) start_unicorn = self.generate_start_unicorn_func() @@ -597,6 +659,6 @@ def generate_class(self): # Put the pieces together self.final = comments + imp + defn + init + emuinit + codevars + datavars + mmaps + writes \ - + hookdict + hooks + start_unicorn + runfns + "\n" + ("x = %s()" % name) \ + + hooker + hookdict + "\n" + hooks + hookcode + start_unicorn + runfns + "\n" + ("x = %s()" % name) \ +"\n"+"print (x.run("+argf+"))\n" diff --git a/gui.py b/gui.py index 1c3ebbd..d623dc4 100644 --- a/gui.py +++ b/gui.py @@ -3,7 +3,10 @@ ''' import sys if (sys.platform == 'win32'): - sys.path.append("C:\\Python37\\lib\\site-packages") + if sys.version_info[0] >= 3: + sys.path.append("C:\\Python37\\lib\\site-packages\\PyQt5") + else: + sys.path.append("C:\\Python27\\lib\\site-packages\\PyQt5") try: from PyQt5 import QtWidgets, QtGui, QtCore from PyQt5.QtCore import Qt diff --git a/packager.py b/packager.py index 2b110f1..1b32db7 100644 --- a/packager.py +++ b/packager.py @@ -5,7 +5,7 @@ ### Global for listing all chunks of code for which we have tried to create a python wrapper. emuchunks = {} - + # List of basic block chunks to package for BB mode bbChunks = [] class Packager(object): @@ -242,6 +242,7 @@ def map_dependent_sections(self, dataRefs): for ref in dataRefs: sections = [self.engine.find_section(ref.address)] secs += sections + for sec_start, sec_end, sec_name in secs: self.codeobj.add_data(self.engine.read_bytes(sec_start, sec_end - sec_start), sec_start) self.codeobj.add_mmap(sec_start) @@ -257,10 +258,10 @@ def resolve_data_dependencies(self, dataRefs): This function handles finding data that needs to get mapped. ''' if (self.dataStrategy == None): - if (self.ui.yes_no_box("Use Page-Marking Mode for data dependencies (default; yes)")): - self.dataStrategy = "page" - else: + if (self.ui.yes_no_box("Use Section-Marking Mode for data dependencies (default; yes)")): self.dataStrategy = "section" + else: + self.dataStrategy = "page" if (self.dataStrategy == "section"): self.map_dependent_sections(dataRefs) From 4d1aff81db736331acf75b17385ce27b0590dc85 Mon Sep 17 00:00:00 2001 From: Bjoern Kerler <info@revskills.de> Date: Thu, 23 Aug 2018 19:57:27 +0200 Subject: [PATCH 4/5] Fix issue #11 --- packager.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packager.py b/packager.py index 1b32db7..c926dcd 100644 --- a/packager.py +++ b/packager.py @@ -239,9 +239,11 @@ def map_dependent_sections(self, dataRefs): print ("Mapping Sections") pagesize = self.engine.get_page_size() secs = [] - for ref in dataRefs: - sections = [self.engine.find_section(ref.address)] - secs += sections + + for ref in dataRefs: + section=self.engine.find_section(ref.address) + if section!=-1: + secs += [section] for sec_start, sec_end, sec_name in secs: self.codeobj.add_data(self.engine.read_bytes(sec_start, sec_end - sec_start), sec_start) From 48e1ecb22aa01e32c738284e24342211437e16f0 Mon Sep 17 00:00:00 2001 From: Bjoern Kerler <info@revskills.de> Date: Thu, 23 Aug 2018 20:53:17 +0200 Subject: [PATCH 5/5] Prevent unsupported code for issue #11 --- analysis_engine.py | 10 ++++++++++ packager.py | 13 +++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/analysis_engine.py b/analysis_engine.py index 2c2e071..878aeec 100644 --- a/analysis_engine.py +++ b/analysis_engine.py @@ -209,6 +209,11 @@ def get_function_bytes(self, address=None, name=None): else: print ("[ripr] No arguments supplied to get_function_bytes") return None + + if self.bv.get_function_at(address)==None: + print ("[ripr] Couldn't get function binary view. Maybe code arch is thumb2?") + return None + # Sort the basic blocks in ascending order bblist = sorted(fobj.basic_blocks, key=lambda x: x.start) map(lambda bb: bb.set_user_highlight(HighlightStandardColor.BlackHighlightColor), bblist) @@ -300,6 +305,9 @@ def branches_from_block(self, block, callCallback, branchCallback): def branches_from_func(self, address, callCallback, branchCallback): fobj = self.bv.get_function_at(address) + if (fobj==None): + return + for block in fobj.low_level_il: self.branches_from_block(block, callCallback, branchCallback) @@ -319,6 +327,8 @@ def get_refs_to(self, address): def function_contains_addr(self, func_addr, testAddr): fobj = self.bv.get_function_at(func_addr) + if (fobj==None): + return False return (fobj.get_basic_block_at(testAddr) != None) def scan_potential_pointers_bb(self, il_block, fobj): diff --git a/packager.py b/packager.py index c926dcd..f9a059d 100644 --- a/packager.py +++ b/packager.py @@ -63,14 +63,17 @@ def minimal_package_function(self, address=None): address = self.address # Get the code to be emulated localCode = self.engine.get_function_bytes(address=address) - + if (localCode == None): + self.ui.msgBox("[ripr] Couldn't get function binary view. Maybe code arch is thumb2?") + return False # Add each contiguous chunk of code to codeobj and make sure # it will be mapped. for startAddr in list(localCode.keys()): self.codeobj.add_mmap(startAddr) self.targetCode.append(localCode) - + return True + def minimal_package_region(self): targetCode = self.engine.get_region_bytes(address=self.address) self.codeobj.add_data(targetCode[0], targetCode[1]) @@ -94,7 +97,8 @@ def package_function(self): if not self.codeobj.name: return # Get the bare minimum required information. - self.minimal_package_function() + if (self.minimal_package_function()==False): + return # Try to find dependencies of our code. self.resolve_dependencies() @@ -274,7 +278,8 @@ def resolve_codeRefs(self, coderefs): for ref in coderefs: print ("Found CodeRef: %x::%s" % (ref.address, ref.type)) if (ref.type == 'call'): - self.minimal_package_function(address=ref.address) + if (self.minimal_package_function(address=ref.address)==False): + continue self.resolve_dependencies(address=ref.address, isFunc=True) def resolve_dependencies(self, address=None, isFunc=None):