From f80e4f3cc399007f9be518b2bc2eb058eb3049d9 Mon Sep 17 00:00:00 2001 From: Axel Tillequin Date: Sat, 16 May 2020 09:40:05 +0200 Subject: [PATCH] update to v2.9.3 * add codeblock view * fix PE32+ DataDirectories * update win64 OS class * update documentation --- README.rst | 2 + amoco/arch/x64/formats.py | 19 +-- amoco/arch/x86/formats.py | 13 +- amoco/cas/expressions.py | 2 +- amoco/cas/mapper.py | 4 +- amoco/config.py | 4 + amoco/logger.py | 2 +- amoco/system/baremetal/psx.py | 1 + amoco/system/core.py | 28 +++- amoco/system/elf.py | 16 ++- amoco/system/linux32/x86.py | 26 ++++ amoco/system/linux64/x64.py | 26 ++++ amoco/system/macho.py | 87 +++++++++++-- amoco/system/osx/x64.py | 23 ++++ amoco/system/pe.py | 134 +++++++++++++++++++ amoco/system/structs.py | 16 ++- amoco/system/win64/x64.py | 34 ++++- amoco/ui/app.py | 6 +- amoco/ui/render.py | 6 +- amoco/ui/srv.py | 18 +++ amoco/ui/views.py | 66 +++++++--- doc/index.rst | 2 +- doc/installation.rst | 4 +- doc/overview.rst | 1 + doc/quickstart.rst | 185 ++++++++++++++++----------- setup.py | 2 +- tests/samples/test.c | 21 +++ tests/samples/x64/test_full.elf64 | Bin 0 -> 10920 bytes tests/samples/x64/test_partial.elf64 | Bin 0 -> 10864 bytes tests/samples/x86/test_full.elf | Bin 0 -> 5464 bytes tests/samples/x86/test_partial.elf | Bin 0 -> 9564 bytes tests/samples/x86/test_pie.elf | Bin 0 -> 9604 bytes tests/test_arch_x86.py | 2 +- 33 files changed, 607 insertions(+), 143 deletions(-) create mode 100644 tests/samples/test.c create mode 100644 tests/samples/x64/test_full.elf64 create mode 100644 tests/samples/x64/test_partial.elf64 create mode 100644 tests/samples/x86/test_full.elf create mode 100644 tests/samples/x86/test_partial.elf create mode 100644 tests/samples/x86/test_pie.elf diff --git a/README.rst b/README.rst index 461f06e..f08d1e1 100644 --- a/README.rst +++ b/README.rst @@ -91,6 +91,8 @@ Changelog * add MIPS architecture (R3000 only) * improve support for changes in config * rework ext/stub interface + * fixing sparc formatter + * fixing PE32+ DataDirectories parsing - `v2.9.2`_ diff --git a/amoco/arch/x64/formats.py b/amoco/arch/x64/formats.py index 97a8588..e5b5060 100644 --- a/amoco/arch/x64/formats.py +++ b/amoco/arch/x64/formats.py @@ -39,10 +39,13 @@ def opers(i): s = [] for op in i.operands: if op._is_mem: - if i.misc["rip_rel"] is not None: - s.append((Token.Memory, "[%s]" % i.misc["rip_rel"])) - else: - s.append((Token.Memory, deref(op))) + if i.misc['rip_rel']: + op = i.misc['rip_rel'] + elif op.a.base._is_reg and op.a.base.etype & regtype.PC: + if i.address is not None: + op = op.__class__(i.address+i.length+op.a.disp,op.size,seg=op.a.seg) + i.misc['rip_rel'] = op + s.append((Token.Memory, deref(op))) elif op._is_cst: if i.misc["imm_ref"] is not None: s.append((Token.Address, str(i.misc["imm_ref"]))) @@ -61,11 +64,11 @@ def opers(i): def oprel(i): to = i.misc["to"] if to is not None: - return [(Token.Address, "*" + str(to))] + return [(Token.Address, str(to))] if (i.address is not None) and i.operands[0]._is_cst: v = i.address + i.operands[0].signextend(64) + i.length i.misc["to"] = v - return [(Token.Address, "*" + str(v))] + return [(Token.Address, str(v))] return [(Token.Constant, ".%+d" % i.operands[0].value)] @@ -150,11 +153,11 @@ def opers_att(i): def oprel_att(i): to = i.misc["to"] if to is not None: - return [(Token.Address, "*" + str(to))] + return [(Token.Address, str(to))] if (i.address is not None) and i.operands[0]._is_cst: v = i.address + i.operands[0].signextend(64) + i.length i.misc["to"] = v - return [(Token.Address, "*" + str(v))] + return [(Token.Address, str(v))] return [(Token.Constant, "$.%+d" % i.operands[0].value)] diff --git a/amoco/arch/x86/formats.py b/amoco/arch/x86/formats.py index b4a2c64..4878184 100644 --- a/amoco/arch/x86/formats.py +++ b/amoco/arch/x86/formats.py @@ -39,6 +39,9 @@ def opers(i): s = [] for op in i.operands: if op._is_mem: + if op.a.base._is_reg and op.a.base.etype & regtype.PC: + if i.address is not None: + op = op.__class__(i.address+i.length+op.a.disp,op.size,seg=op.a.seg) s.append((Token.Memory, deref(op))) elif op._is_cst: if i.misc["imm_ref"] is not None: @@ -58,11 +61,11 @@ def opers(i): def oprel(i): to = i.misc["to"] if to is not None: - return [(Token.Address, "*" + str(to))] + return [(Token.Address, str(to))] if (i.address is not None) and i.operands[0]._is_cst: v = i.address + i.operands[0].signextend(32) + i.length i.misc["to"] = v - return [(Token.Address, "*" + str(v))] + return [(Token.Address, str(v))] return [(Token.Constant, ".%+d" % i.operands[0].value)] @@ -147,11 +150,11 @@ def opers_att(i): def oprel_att(i): to = i.misc["to"] if to is not None: - return [(Token.Address, "*" + str(to))] + return [(Token.Address, str(to))] if (i.address is not None) and i.operands[0]._is_cst: v = i.address + i.operands[0].signextend(32) + i.length i.misc["to"] = v - return [(Token.Address, "*" + str(v))] + return [(Token.Address, str(v))] return [(Token.Constant, "$.%+d" % i.operands[0].value)] @@ -849,7 +852,7 @@ def att_opers(i, operands=None): def att_oprel(i): to = i.misc["to"] if to is not None: - return [(Token.Address, "*" + str(to))] + return [(Token.Address, str(to))] op = i.operands[0] if op._is_lab: return [(Token.Address, str(op.ref))] diff --git a/amoco/cas/expressions.py b/amoco/cas/expressions.py index c50a63f..3ef35ff 100644 --- a/amoco/cas/expressions.py +++ b/amoco/cas/expressions.py @@ -1505,7 +1505,7 @@ def __init__(self, x, pos, size, ref=None): def setref(self, ref): if self.x._is_reg: - self.etype |= et_reg + self.etype |= self.x.etype if ref is None: ref = self.x._subrefs.get((self.pos, self.size), None) else: diff --git a/amoco/cas/mapper.py b/amoco/cas/mapper.py index 7d85c25..53c8587 100644 --- a/amoco/cas/mapper.py +++ b/amoco/cas/mapper.py @@ -74,9 +74,11 @@ def inputs(self): "list antecedent locations (used in the mapping)" r = [] for l, v in iter(self.__map.items()): + if (l==v): + continue for lv in locations_of(v): if lv._is_reg and l._is_reg: - if (lv == l) or (lv.etype & l.etype & regtype.FLAGS): + if (lv.etype & l.etype & regtype.FLAGS): continue r.append(lv) return r diff --git a/amoco/config.py b/amoco/config.py index 6a1c8ad..6bd8516 100644 --- a/amoco/config.py +++ b/amoco/config.py @@ -17,8 +17,10 @@ - 'helper' will use codeblock helper functions to pretty print code if True (default) - 'header' will show a dashed header line including the address of the block if True (default) - 'footer' will show a dashed footer line if True + - 'segment' will show memory section/segment name in codeblock view if True (default) - 'bytecode' will show the hex encoded bytecode string of every instruction if True (default) - 'padding' will add the specified amount of blank chars to between address/bytecode/instruction (default 4). + - 'hist' number of instruction's history shown in emulator view (default 3). - 'Cas' which deals with parameters of the algebra system: @@ -89,6 +91,7 @@ class Code(Configurable): helper (Bool): use block helpers if True. header (Bool): display block header dash-line with its name if True. footer (Bool): display block footer dash-line if True. + segment (Bool): display memory section/segment name if True. bytecode (Bool): display instructions' bytes. padding (int): add space-padding bytes to bytecode (default=4). hist (int): number of history instructions to show in @@ -98,6 +101,7 @@ class Code(Configurable): header = Bool(True, config=True) footer = Bool(True, config=True) bytecode = Bool(True, config=True) + segment = Bool(True, config=True) padding = Integer(4, config=True) hist = Integer(3, config=True) diff --git a/amoco/logger.py b/amoco/logger.py index 36a17cd..93dde01 100644 --- a/amoco/logger.py +++ b/amoco/logger.py @@ -30,7 +30,7 @@ Setting all modules loggers to ``'ERROR'`` level:: - In [2]: amoco.set_quiet() + In [2]: amoco.logger.set_quiet() Note: All loggers can be configured to log both to *stderr* with selected level diff --git a/amoco/system/baremetal/psx.py b/amoco/system/baremetal/psx.py index 4198b11..3c27490 100644 --- a/amoco/system/baremetal/psx.py +++ b/amoco/system/baremetal/psx.py @@ -79,6 +79,7 @@ def __init__(self, conf=None): conf = System() self.tasks = [] self.abi = None + self.symbols = {} @classmethod def loader(cls, bprm, conf=None): diff --git a/amoco/system/core.py b/amoco/system/core.py index 3891f5d..f841982 100644 --- a/amoco/system/core.py +++ b/amoco/system/core.py @@ -135,6 +135,27 @@ def read_instruction(self, vaddr, **kargs): i.xdata(i, xdata) return i + def symbol_for(self,address): + info = None + if address in self.bin.variables: + info = self.bin.variables[address] + if isinstance(info,tuple): + info = info[0] + info = "$%s"%info + elif address in self.bin.functions: + info = self.bin.functions[address] + if isinstance(info,tuple): + info = info[0] + info = "<%s>"%info + elif self.OS and (address in self.OS.symbols): + info = self.OS.symbols[address] + info = "#%s"%info + return info or "" + + def segment_for(self,address,stype=None): + s = self.bin.getinfo(address)[0] + return s.name if hasattr(s,'name') else "" + def getx(self, loc, size=8, sign=False): """ high level method to get the expressions value associated @@ -261,8 +282,8 @@ class BinFormat(object): symtab = None strtab = None reltab = None - functions = None - variables = None + functions = {} + variables = {} @property def entrypoints(self): @@ -275,6 +296,9 @@ def filename(self): def loadsegment(self, S, pagesize=None, raw=None): raise NotImplementedError + def getinfo(self, target): + return (None, 0, 0) + class DataIO(BinFormat): """ diff --git a/amoco/system/elf.py b/amoco/system/elf.py index 5dda95c..c762621 100644 --- a/amoco/system/elf.py +++ b/amoco/system/elf.py @@ -160,14 +160,13 @@ def getinfo(self, target): # but this may lead to errors because what really matters are segments # loaded by the kernel binfmt_elf.c loader. if self.Shdr: - for s in self.Shdr[::-1]: - if s.sh_type == SHT_NULL: + for s in reversed(self.Shdr): + if s.sh_type != SHT_PROGBITS: continue if s.sh_addr <= addr < s.sh_addr + s.sh_size: return s, addr - s.sh_addr, s.sh_addr - ## elif self.Phdr: - for s in self.Phdr[::-1]: + for s in reversed(self.Phdr): if s.p_type != PT_LOAD: continue if s.p_vaddr <= addr < s.p_vaddr + s.p_filesz: @@ -423,7 +422,8 @@ def checksec(self): R["PIE"] = 1 R["Full RelRO"] = 0 for d in self.readsection(".dynamic") or []: - if d.d_tag == DT_BIND_NOW: + if d.d_tag == DT_BIND_NOW or\ + (d.d_tag == DT_FLAGS and d.d_un==DF_BIND_NOW): R["Full RelRO"] = 1 break return R @@ -1156,6 +1156,12 @@ def DT_ADDRTAGIDX(self, tag): DT_ADDRRNGHI = 0x6FFFFEFF DT_ADDRNUM = 10 + DF_ORIGIN = 0x1 + DF_SYMBOLIC = 0x2 + DF_TEXTREL = 0x4 + DF_BIND_NOW = 0x8 + DF_STATIC_TLS = 0x10 + @StructDefine( """ diff --git a/amoco/system/linux32/x86.py b/amoco/system/linux32/x86.py index ae9df81..13e2342 100644 --- a/amoco/system/linux32/x86.py +++ b/amoco/system/linux32/x86.py @@ -76,6 +76,7 @@ def __init__(self, conf=None): self.abi = cdecl self.tasks = [] + self.symbols = {} @classmethod def loader(cls, bprm, conf=None): @@ -126,6 +127,31 @@ def load_elf_interp(self, p, interp): xf = cpu.ext(f, size=32) xf.stub = p.OS.stub(f) p.state.mmap.write(k, xf) + # we want to add .plt addresses as symbols as well + # to improve asm block views: + plt = got = None + for s in p.bin.Shdr: + if s.name=='.plt': + plt = s + elif s.name=='.got': + got = s + if plt and got: + address = plt.sh_addr + pltco = p.bin.readsection(plt) + while(pltco): + i = p.cpu.disassemble(pltco) + if i.mnemonic=='JMP' and i.operands[0]._is_mem: + target = i.operands[0].a + if target.base is p.cpu.eip: + target = address+target.disp + elif target.base._is_reg: + target = got.sh_addr+target.disp + elif target.base._is_cst: + target = target.base.value+target.disp + if target in p.bin.functions: + p.bin.functions[address] = p.bin.functions[target] + pltco = pltco[i.length:] + address += i.length def stub(self, refname): return self.stubs.get(refname, self.default_stub) diff --git a/amoco/system/linux64/x64.py b/amoco/system/linux64/x64.py index 30d2c5c..8afd77b 100644 --- a/amoco/system/linux64/x64.py +++ b/amoco/system/linux64/x64.py @@ -77,6 +77,7 @@ def __init__(self, conf=None): self.NX = conf.nx self.tasks = [] self.abi = None + self.symbols = {} @classmethod def loader(cls, bprm, conf=None): @@ -136,6 +137,31 @@ def load_elf_interp(self, p, interp): xfunc = cpu.ext(f, size=64) xfunc.stub = p.OS.stub(f) p.state.mmap.write(k, xfunc) + # we want to add .plt addresses as symbols as well + # to improve asm block views: + plt = got = None + for s in p.bin.Shdr: + if s.name=='.plt': + plt = s + elif s.name=='.got': + got = s + if plt and got: + address = plt.sh_addr + pltco = p.bin.readsection(plt) + while(pltco): + i = p.cpu.disassemble(pltco) + if i.mnemonic=='JMP' and i.operands[0]._is_mem: + target = i.operands[0].a + if target.base is p.cpu.rip: + target = address+target.disp + elif target.base._is_reg: + target = got.sh_addr+target.disp + elif target.base._is_cst: + target = target.base.value+target.disp + if target in p.bin.functions: + p.bin.functions[address] = p.bin.functions[target] + pltco = pltco[i.length:] + address += i.length def stub(self, refname): return self.stubs.get(refname, self.default_stub) diff --git a/amoco/system/macho.py b/amoco/system/macho.py index 5ae7e8c..0c6e61d 100644 --- a/amoco/system/macho.py +++ b/amoco/system/macho.py @@ -16,6 +16,7 @@ from amoco.system.utils import read_uleb128 from amoco.system.structs import Consts, StructFormatter, default_formatter from amoco.system.structs import StructDefine, UnionDefine +from amoco.system.structs import token_name_fmt, token_flag_fmt, Token, highlight from amoco.logger import Log @@ -168,11 +169,45 @@ def getinfo(self, target): res = (None, 0, 0) for c in self.cmds: if c.cmd in (LC_SEGMENT, LC_SEGMENT_64,): - if c.vmaddr <= target <= (c.vmaddr + c.vmsize): + if c.vmaddr <= target < (c.vmaddr + c.vmsize): res = (c, (target - c.vmaddr), c.vmaddr) + for s in c.sections: + if s.addr <= target < s.addr+s.size_: + res = (s, (target - s.addr), s.addr) + break break return res + def checksec(self): + "check for usual OSX security features." + R = {} + R['Arc'] = False + R["Canary"] = False + for f in iter(self.la_symbol_ptr.values()): + if f == b"___stack_chk_fail" or\ + f == b"___stack_chk_guard": + R["Canary"] = True + elif f==b'_objc_release': + R["Arc"] = True + R['Signature'] = False + R['Encrypted'] = False + R['Restrict'] = False + R['RPath'] = False + for c in self.cmds: + if c.cmd == LC_CODE_SIGNATURE: + R['Signature'] = True + elif c.cmd in (LC_ENCRYPTION_INFO, LC_ENCRYPTION_INFO_64): + R['Encrypted'] = True + elif c.cmd in (LC_SEGMENT, LC_SEGMENT_64): + if c.segname.decode().strip('\0').lower()=="__restrict": + R['Restrict'] = True + elif c.cmd == LC_RPATH: + R['RPath'] = True + R["NX (heap)"] = (self.header.flags & MH_NO_HEAP_EXECUTION)!=0 + R["NX (stack)"] = (self.header.flags & MH_ALLOW_STACK_EXECUTION)==0 + R["PIE"] = (self.header.flags & MH_PIE)!=0 + return R + def data(self, target, size): "returns 'size' bytes located at target virtual address" return self._readcode(target, size)[0] @@ -185,25 +220,57 @@ def _readcode(self, target, size): def getfileoffset(self, target): "converts given target virtual address back to offset in file" s, offset, _ = self.getinfo(target) + fileoffset = s.fileoffset if hasattr(s,'fileoffset') else s.offset return s.fileoffset + offset def readsegment(self, S): - "returns segment S data from file" - self.__file.seek(S.fileoffset) - return self.__file.read(S.filesize) + "returns data of segment/section S" + try: + self.__file.seek(S.fileoffset) + return self.__file.read(S.filesize) + except AttributeError: + self.__file.seek(S.offset) + return self.__file.read(S.size_) def loadsegment(self, S, pagesize=None): - "returns segment S data padded/aligned to S.vmsize and pagesize" + "returns padded & aligned data of segment/section S" s = self.readsegment(S) if pagesize is None: - pagesize = S.vmsize - n, r = divmod(S.vmsize, pagesize) + if hasattr(S,'vmsize'): + size = pagesize = S.vmsize + else: + size = S.size_ + pagesize = S.align + n, r = divmod(size, pagesize) if r > 0: n += 1 return s.ljust(n * pagesize, b"\0") - def readsection(self, s): - raise NotImplementedError + def readsection(self, sect): + "returns the segment/section data bytes matching given sect name" + if isinstance(sect,bytes): + sect = sect.decode() + if isinstance(sect, str): + for c in self.cmds: + if c.cmd in (LC_SEGMENT,LC_SEGMENT_64): + for s in c.sections: + if s.sectname.decode().strip("\0")==sect: + return self.readsegment(s) + else: + return self.readsegment(sect) + return None + + def getsection(self, sect): + "returns the segment/section matching given sect name" + if isinstance(sect,bytes): + sect = sect.decode() + if isinstance(sect, str): + for c in self.cmds: + if c.cmd in (LC_SEGMENT,LC_SEGMENT_64): + for s in c.sections: + if s.sectname.decode().strip("\0")==sect: + return s + return None def __read_table(self, off, elt, count, sz=0): tab = [] @@ -892,6 +959,7 @@ def __init__(self, data=None, offset=0): self.address_formatter("reloff") if data: self.unpack(data, offset) + self.name = self.segname.decode().strip('\0') # ------------------------------------------------------------------------------ @@ -921,6 +989,7 @@ def __init__(self, data=None, offset=0): self.address_formatter("reloff") if data: self.unpack(data, offset) + self.name = self.segname.decode().strip('\0') # ------------------------------------------------------------------------------ diff --git a/amoco/system/osx/x64.py b/amoco/system/osx/x64.py index 5d3a81b..8515007 100644 --- a/amoco/system/osx/x64.py +++ b/amoco/system/osx/x64.py @@ -34,6 +34,7 @@ def __init__(self, conf=None): self.NX = conf.nx self.tasks = [] self.abi = None + self.symbols = {} @classmethod def loader(cls, bprm, conf=None): @@ -91,6 +92,28 @@ def load_macho_interp(self, p, interp): xfunc = cpu.ext(f, size=64) xfunc.stub = p.OS.stub(f) p.state.mmap.write(k, xfunc) + # we want to add stubs addresses as symbols as well + # to improve asm block views: + p.bin.functions.update(p.bin.la_symbol_ptr) + got = None + plt = p.bin.getsection('__stubs') + if plt: + address = plt.addr + pltco = p.bin.readsection(plt) + while(pltco): + i = p.cpu.disassemble(pltco) + if i.mnemonic=='JMP' and i.operands[0]._is_mem: + target = i.operands[0].a + if target.base is p.cpu.rip: + target = address+i.length+target.disp + elif target.base._is_reg: + target = got.sh_addr+target.disp + elif target.base._is_cst: + target = target.base.value+target.disp + if target in p.bin.functions: + p.bin.functions[address] = p.bin.functions[target] + pltco = pltco[i.length:] + address += i.length def stub(self, refname): return self.stubs.get(refname, self.default_stub) diff --git a/amoco/system/pe.py b/amoco/system/pe.py index 6e8c856..55e028f 100644 --- a/amoco/system/pe.py +++ b/amoco/system/pe.py @@ -97,6 +97,31 @@ def __init__(self, data): self.variables = self.__variables() self.tls = self.__tls() + def checksec(self): + R = {} + dllc = self.Opt.DllCharacteristics + dynamic_base = dllc & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE + R['ASLR'] = "DYNAMIC_BASE" if dynamic_base else False + he = dllc & IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA + if he: + R['ASLR'] = "HIGH_ENTROPY_VA" + R['CFG'] = (dllc & IMAGE_DLLCHARACTERISTICS_GUARD_CF)!=0 + R['DEP'] = (dllc & IMAGE_DLLCHARACTERISTICS_NX_COMPAT)!=0 + R['Isolation'] = (dllc & IMAGE_DLLCHARACTERISTICS_NO_ISOLATION)==0 + R['SEH'] = (dllc & IMAGE_DLLCHARACTERISTICS_NO_SEH)==0 + cth = self.__LoadConfigTable() + if cth: + if cth.SEHandlerCount>0: + R['Safe-SEH'] = True + if (cth.GuardFlags & IMAGE_GUARD_RF_INSTRUMENTED) and\ + ((cth.GuardFlags & IMAGE_GUARD_RF_ENABLE) or\ + (cth.GuardFlags & IMAGE_GUARD_RF_STRICT)): + R['RFG'] = True + else: + R['Safe-SEH'] = False + R['RFG'] = False + return R + def locate(self, addr, absolute=False): """ returns a tuple with: @@ -239,6 +264,18 @@ def __tls(self): return tls return None + def __LoadConfigTable(self): + lct = self.Opt.DataDirectories.get("LoadConfigTable", None) + if lct is not None and lct.RVA != 0: + try: + data = self.getdata(lct.RVA) + except ValueError: + logger.warning("invalid LoadLConfigTable RVA") + else: + lct = LoadConfigTable(data, self.Opt.Magic) + return lct + return None + def __variables(self): D = {} return D @@ -398,6 +435,7 @@ def __init__(self, data=None, offset=0): class OptionalHdr(StructFormatter): def __init__(self, data=None, offset=0): self.name_formatter("Magic") + self.name_formatter("Subsystem") self.address_formatter( "AddressOfEntryPoint", "BaseOfCode", "BaseOfData", "ImageBase" ) @@ -457,6 +495,35 @@ def __len__(self): OPTIONAL_HEADER_MAGIC_PE = 0x10B OPTIONAL_HEADER_MAGIC_PE_PLUS = 0x20B +with Consts("Subsystem"): + IMAGE_SUBSYSTEM_UNKOWN = 0 + IMAGE_SUBSYSTEM_NATIVE = 1 + IMAGE_SUBSYSTEM_WINDOWS_GUI = 2 + IMAGE_SUBSYSTEM_WINDOWS_CUI = 3 + IMAGE_SUBSYSTEM_OS2_CUI = 5 + IMAGE_SUBSYSTEM_POSIX_CUI = 7 + IMAGE_SUBSYSTEM_NATIVE_WINDOWS = 8 + IMAGE_SUBSYSTEM_WINDOWS_CE_GUI = 9 + IMAGE_SUBSYSTEM_EFI_APPLICATION = 10 + IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER= 11 + IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER= 12 + IMAGE_SUBSYSTEM_EFI_ROM = 13 + IMAGE_SUBSYSTEM_XBOX = 14 + IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION = 16 + +with Consts("DllCharacteristics"): + IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020 + IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040 + IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY = 0x0080 + IMAGE_DLLCHARACTERISTICS_NX_COMPAT = 0x0100 + IMAGE_DLLCHARACTERISTICS_NO_ISOLATION = 0x0200 + IMAGE_DLLCHARACTERISTICS_NO_SEH = 0x0400 + IMAGE_DLLCHARACTERISTICS_NO_BIND = 0x0800 + IMAGE_DLLCHARACTERISTICS_APPCONTAINER = 0x1000 + IMAGE_DLLCHARACTERISTICS_WDM_DRIVER = 0x2000 + IMAGE_DLLCHARACTERISTICS_GUARD_CF = 0x4000 + IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000 + IMAGE_DIRECTORY_ENTRY_EXPORT = 0 IMAGE_DIRECTORY_ENTRY_IMPORT = 1 IMAGE_DIRECTORY_ENTRY_RESOURCE = 2 @@ -998,3 +1065,70 @@ def readcallbacks(self, data): # ------------------------------------------------------------------------------ + +with Consts("GuardFlags"): + IMAGE_GUARD_CF_INSTRUMENTED = 0x00000100 + IMAGE_GUARD_CFW_INSTRUMENTED = 0x00000200 + IMAGE_GUARD_CF_FUNCTION_TABLE_PRESENT = 0x00000400 + IMAGE_GUARD_SECURITY_COOKIE_UNUSED = 0x00000800 + IMAGE_GUARD_PROTECT_DELAYLOAD_IAT = 0x00001000 + IMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION = 0x00002000 + IMAGE_GUARD_CF_EXPORT_SUPPRESSION_INFO_PRESENT = 0x00004000 + IMAGE_GUARD_CF_ENABLE_EXPORT_SUPPRESSION = 0x00008000 + IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT = 0x00010000 + IMAGE_GUARD_RF_INSTRUMENTED = 0x00020000 + IMAGE_GUARD_RF_ENABLE = 0x00040000 + IMAGE_GUARD_RF_STRICT = 0x00080000 + IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK = 0xF0000000 + IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT = 28 + +@StructDefine( + """ +I : Characteristics +I : TimeDateStamp +H : MajorVersion +H : MinorVersion +I : GlobalFlagsClear +I : GlobalFlagsSet +I : CriticalSectionDefaultTimeout +I : DeCommitFreeBlockThreshold +I : DeCommitTotalFreeThreshold +I : LockPrefixTable +I : MaximumAllocationSize +I : VirtualMemoryThreshold +I : ProcessAffinityMask +I : ProcessHeapFlags +H : CSDVersion +H : reserved +I : EditList +I : SecurityCookie +I : SEHandlerTable +I : SEHandlerCount +I : GuardCFCheckFunctionPointer +I : GuardCFDispatchFunctionPointer +I : GuardCFFunctionTable +I : GuardCFFunctionCount +I : GuardFlags +I*3: CodeIntegrity +I : GuardAddressTakenIatEntryTable +I : GuardAddressTakenIatEntryCount +I : GuardLongJumpTargetTable +I : GuardLongJumpTargetCount +""" +) +class LoadConfigTable(StructFormatter): + def __init__(self, data, magic): + size = {0x20B: 64, 0x10B: 32}[magic] + self.elsize = size // 8 + if magic == 0x20B: + for i in (7,8,9,10,11,12,16,17,18,19,20,21,22,23, + 26,27,28,29): + self.fields[i].typename = "Q" + self.flag_formatter("Characteristics", + "ProcessHeapFlags", + "GuardFlags", + ) + if data: + self.unpack(data) + + diff --git a/amoco/system/structs.py b/amoco/system/structs.py index 38edab6..16493d2 100644 --- a/amoco/system/structs.py +++ b/amoco/system/structs.py @@ -745,7 +745,21 @@ def size(cls): return sz def __len__(self): - return self.size() + """size method is a class method, __len__ computes + the actual size of the instance""" + A = self.align_value() + sz = 0 + for f in self.fields: + if self.union is False and not self.packed: + sz = f.align(sz) + if self.union is False: + sz += f.size() + elif f.size > sz: + sz = f.size() + r = sz % A + if (not self.packed) and r > 0: + sz += A - r + return sz def __eq__(self, other): if ( diff --git a/amoco/system/win64/x64.py b/amoco/system/win64/x64.py index b7d8430..8e849f4 100644 --- a/amoco/system/win64/x64.py +++ b/amoco/system/win64/x64.py @@ -6,11 +6,30 @@ from amoco.system.pe import * from amoco.system.core import CoreExec -from amoco.code import tag, xfunc +from amoco.code import tag import amoco.arch.x64.cpu_x64 as cpu # ------------------------------------------------------------------------------ +with Consts("COFFRelocation.Type"): + IMAGE_REL_AMD64_ABSOLUTE = 0x0000 + IMAGE_REL_AMD64_ADDR64 = 0x0001 + IMAGE_REL_AMD64_ADDR32 = 0x0002 + IMAGE_REL_AMD64_ADDR32NB = 0x0003 + IMAGE_REL_AMD64_REL32 = 0x0004 + IMAGE_REL_AMD64_REL32_1 = 0x0005 + IMAGE_REL_AMD64_REL32_2 = 0x0006 + IMAGE_REL_AMD64_REL32_3 = 0x0007 + IMAGE_REL_AMD64_REL32_4 = 0x0008 + IMAGE_REL_AMD64_REL32_5 = 0x0009 + IMAGE_REL_AMD64_SECTION = 0x000A + IMAGE_REL_AMD64_SECREL = 0x000B + IMAGE_REL_AMD64_SECREL7 = 0x000C + IMAGE_REL_AMD64_TOKEN = 0x000D + IMAGE_REL_AMD64_SREL32 = 0x000E + IMAGE_REL_AMD64_PAIR = 0x000F + IMAGE_REL_AMD64_SSPAN32 = 0x0010 + class OS(object): """OS class is a provider for all the environment in which a Task runs. @@ -33,6 +52,8 @@ def __init__(self, conf=None): self.ASLR = conf.aslr self.NX = conf.nx self.tasks = [] + self.abi = None + self.symbols = {} @classmethod def loader(cls, pe, conf=None): @@ -57,6 +78,15 @@ def load_pe_binary(self, pe): p.state[cpu.rdx] = cpu.cst(0, 64) p.state[cpu.rsi] = cpu.cst(0, 64) p.state[cpu.rdi] = cpu.cst(0, 64) + p.state[cpu.r8] = cpu.cst(0, 64) + p.state[cpu.r9] = cpu.cst(0, 64) + p.state[cpu.r10] = cpu.cst(0, 64) + p.state[cpu.r11] = cpu.cst(0, 64) + p.state[cpu.r12] = cpu.cst(0, 64) + p.state[cpu.r13] = cpu.cst(0, 64) + p.state[cpu.r14] = cpu.cst(0, 64) + p.state[cpu.r15] = cpu.cst(0, 64) + p.state[cpu.rflags] = cpu.cst(0, 64) # create the stack space: if self.ASLR: p.state.mmap.newzone(p.cpu.rsp) @@ -64,7 +94,7 @@ def load_pe_binary(self, pe): ssz = pe.Opt.SizeOfStackReserve stack_base = 0x00007FFFFFFFFFFF & ~(self.PAGESIZE - 1) p.state.mmap.write(stack_base - ssz, b"\0" * ssz) - p.state[cpu.esp] = cpu.cst(stack_base, 64) + p.state[cpu.rsp] = cpu.cst(stack_base, 64) # create the dynamic segments: if len(pe.functions) > 0: self.load_pe_iat(p) diff --git a/amoco/ui/app.py b/amoco/ui/app.py index 26d739b..4be1c1e 100644 --- a/amoco/ui/app.py +++ b/amoco/ui/app.py @@ -103,11 +103,11 @@ def cli(ctx, verbose, debug, quiet, configfile): amoco.conf.load(configfile) c = amoco.conf if quiet: - amoco.set_quiet() + amoco.logger.set_quiet() if verbose: - amoco.set_log_all("VERBOSE") + amoco.logger.set_log_all("VERBOSE") if debug: - amoco.set_debug() + amoco.logger.set_debug() if verbose | debug: if c.src: click.echo("config file '%s' loaded" % c.f) diff --git a/amoco/ui/render.py b/amoco/ui/render.py index e5f5cc7..b3a088d 100644 --- a/amoco/ui/render.py +++ b/amoco/ui/render.py @@ -67,7 +67,8 @@ def format(self, tokensource, outfile): Token.Register : "#33f", Token.Memory : "#3ff", Token.String : "#3f3", - Token.Comment : "#8f8", + Token.Segment : "#888", + Token.Comment : "#f8f", Token.Green : "#8f8", Token.Good : "bold #8f8", Token.Name : "bold", @@ -96,7 +97,8 @@ class DarkStyle(Style): Token.Register : "#00f", Token.Memory : "#00c0c0", Token.String : "#008800", - Token.Comment : "#333", + Token.Segment : "#888", + Token.Comment : "#a3a", Token.Green : "#008800", Token.Good : "bold #008800", Token.Name : "bold", diff --git a/amoco/ui/srv.py b/amoco/ui/srv.py index 5eb759b..4fa43f5 100644 --- a/amoco/ui/srv.py +++ b/amoco/ui/srv.py @@ -513,3 +513,21 @@ def run(srv, args): srv.msgs.put('error: no task loaded') return 0 + +# ----------------------------------------------------------------------------- + + +@DefineSrvCommand(srv,"checksec") +class cmd_checksec(object): + "checksec: security parameters related to the current task." + + @staticmethod + def run(srv, args): + if srv.obj: + srv.msgs.put(str(srv.obj.task.view.checksec)) + else: + srv.msgs.put('error: no task loaded') + return 0 + + +# ----------------------------------------------------------------------------- diff --git a/amoco/ui/views.py b/amoco/ui/views.py index 51bea9b..f838628 100644 --- a/amoco/ui/views.py +++ b/amoco/ui/views.py @@ -118,7 +118,8 @@ def instr(i, flavor=None): except TypeError: b = "'%s'"%("--"*(i.length)) ins = [ - (Token.Address, "{:<20}".format(str(i.address))), + (Token.Address, "{}".format(str(i.address))), + (Token.Literal, " "), (Token.Column, ""), ] if conf.Code.bytecode: @@ -293,21 +294,12 @@ def checksec(self): t.rowparams["sep"] = icons.sep s = self.of.bin.checksec() tokattr = lambda v: Token.Good if v else Token.Alert - t.addrow([ - (tokattr(s["Canary"]), "Canary: %d"%s["Canary"]), - (Token.Column, ""), - (tokattr(s["NX"]), "NX: %d"%s["NX"]), - (Token.Column, " "), - (tokattr(s["PIE"]), "PIE: %d"%s["PIE"]), - (Token.Column, ""), - (tokattr(s["Fortify"]), "Fortify: %d"%s["Fortify"]), - (Token.Column, ""), - (tokattr(s["Partial RelRO"]), - "Partial RelRO: %d"%s["Partial RelRO"]), - (Token.Column, ""), - (tokattr(s["Full RelRO"]), - "Full RelRO: %d"%s["Full RelRO"]), - ]) + r = [] + for k,v in s.items(): + r.append((tokattr(v), "%s: %s"%(k,v))) + r.append((Token.Column,"")) + r.pop() + t.addrow(r) else: t="" return t @@ -362,6 +354,42 @@ def memory(self,start,nbl=1,nbc=1,w=None): t.addrow(r) return t + def code(self,blk): + if not isinstance(blk,vltable): + T = blk.view._vltable() + else: + T = blk + for r in T.rows: + for c in r.cols[2:]: #skip address and bytecode columns + for i in range(len(c)-1,-1,-1): + tn,tv = c[i] + if tn in (Token.Address,Token.Constant): + try: + v = int(tv,0) + tv = self.of.symbol_for(v) + except ValueError: + tv = None + if tv: + c.insert(i+1,(Token.Comment,tv)) + if conf.Code.segment: + try: + address = int(r.cols[0][0][1],0) + segname = self.of.segment_for(address) + except ValueError: + segname = None + if segname: + r.cols[0].insert(1,(Token.Segment,segname)) + T.update() + if conf.Code.bytecode: + pad = conf.Code.padding + T.colsize[1] += pad + if T.header: + T.header = T.header.ljust(T.width, icons.hor) + if T.footer: + T.footer = T.footer.ljust(T.width, icons.hor) + return T + + # ------------------------------------------------------------------------------- @@ -438,10 +466,8 @@ def frame_code(self): if i.address < here: T.rows.insert(0,tokenrow(blockView.instr(i))) here = i.address - T.update() - if conf.Code.bytecode: - pad = conf.Code.padding - T.colsize[1] += pad + T = self.of.task.view.code(T) + T.header = T.footer = "" rest = self.term.width - T.width T.addcolsize(-1,rest) t.append(str(T)) diff --git a/doc/index.rst b/doc/index.rst index 1772f8c..4967f02 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -4,7 +4,7 @@ Amoco documentation =================== -Amoco is a python (>=3.5) package dedicated to the static symbolic +Amoco is a python (>=3.7) package dedicated to the static symbolic analysis of binary programs. It features: diff --git a/doc/installation.rst b/doc/installation.rst index fa7790c..816c0e3 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -17,7 +17,7 @@ Recommended *optional* packages are: Some optional features related to UI and persistence require: - click_ used to define amoco command-line app -- blessings_ used for terminal based debugger frontend +- blessed_ used for terminal based debugger frontend - tqdm_ used for terminal based debugger frontend - ply_ for parsing *GNU as* files - sqlalchemy_ for persistence of amoco objects in a database @@ -39,7 +39,7 @@ If you want to use the graphical interface you will need **all** packages. .. _pygments: http://pygments.org/ .. _ccrawl: https://github.com/bdcht/ccrawl .. _click: https://click.palletsprojects.com/ -.. _blessings: https://github.com/erikrose/blessings +.. _blessed: https://github.com/jquast/blessed .. _tqdm: https://github.com/tqdm/tqdm .. _ply: http://www.dabeaz.com/ply/ .. _sqlalchemy: http://www.sqlalchemy.org/ diff --git a/doc/overview.rst b/doc/overview.rst index 8a50cd9..8a42325 100644 --- a/doc/overview.rst +++ b/doc/overview.rst @@ -11,6 +11,7 @@ Amoco is composed of 5 sub-packages - x86, x64 - armv7, armv8 (aarch64) - sparc (v8) + - MIPS (R3000) - riscv - msp430 - avr diff --git a/doc/quickstart.rst b/doc/quickstart.rst index ea55547..b724efc 100644 --- a/doc/quickstart.rst +++ b/doc/quickstart.rst @@ -53,9 +53,14 @@ For example, from directory ``amoco/tests``, do:: e_shnum :30 e_shstrndx :27 -If the binary data uses a supported executable format -(currently :mod:`system.pe`, :mod:`system.elf` or an -HEX/SREC format in :mod:`system.utils`) and targets a +If you have the click_ python package installed, you can also +rely on the ``amoco`` shell command and simply do:: + + % amoco load samples/x86/flow.elf + +If the binary data uses a registered executable format +(currently :mod:`system.pe`, :mod:`system.elf`, :mod:`system.macho` +or an HEX/SREC format in :mod:`system.utils`) and targets a supported plateform (see :ref:`system ` and :ref:`arch ` packages), the returned object is an *abstraction* of the memory mapped program:: @@ -84,8 +89,10 @@ an *abstraction* of the memory mapped program:: > -Note that it is also possible to provide a *raw* bytes -string as input and then manually load the architecture:: +(other more specific executable formats are supported +but they need to be loaded manually.) +Also note that it is possible to provide a *raw* bytes string as input and +then manually load the architecture:: In [1]: import amoco In [2]: shellcode = (b"\xeb\x16\x5e\x31\xd2\x52\x56\x89\xe1\x89\xf3\x31\xc0\xb0\x0b\xcd" @@ -127,17 +134,17 @@ example:: If a mapped binary program has been instanciated, we can start disassembling instructions or *data* located at some virtual address:: - In [12]: print(p.read_instruction(p.cpu.cst(0x4000,32))) + In [12]: print(p.read_instruction(0x4000)) jmp *0x4018 - In [13]: p.read_data(p.cpu.cst(0x4000,32),2) + In [13]: p.read_data(0x4000,2) Out[13]: ['\xeb\x16'] Now, rather than manually adjusting the address to fetch the next instruction, we can use any of the code analysis strategies implemented in amoco to disassemble *basic blocks* directly:: - In [1]: import amoco.sa - In [2]: p = amoco.load_program(u'samples/x86/flow.elf') + % amoco load samples/x86/flow.elf + [...] In [3]: z = amoco.sa.lsweep(p) In [4]: z.getblock(0x8048380) @@ -145,24 +152,47 @@ can use any of the code analysis strategies implemented in amoco to disassemble In [5]: b=_ In [6]: print(b.view) - # --- block 0x8048380 ------------------------------------------ - 0x8048380 '31ed' xor ebp, ebp - 0x8048382 '5e' pop esi - 0x8048383 '89e1' mov ecx, esp - 0x8048385 '83e4f0' and esp, 0xfffffff0 - 0x8048388 '50' push eax - 0x8048389 '54' push esp - 0x804838a '52' push edx - 0x804838b '6810860408' push #__libc_csu_fini - 0x8048390 '68a0850408' push #__libc_csu_init - 0x8048395 '51' push ecx - 0x8048396 '56' push esi - 0x8048397 '68fd840408' push #main - 0x804839c 'e8cfffffff' call *0x8048370 - -Note that when a :class:`block ` is constructed from a mapped binary program instance, -instructions operands will possibly be represented as a symbol (provided by the program's -symbol table) or an absolute virtual address for branching instructions. + ─────────── block 0x8048380 ────────────────────────── + 0x8048380 '31ed' xor ebp, ebp + 0x8048382 '5e' pop esi + 0x8048383 '89e1' mov ecx, esp + 0x8048385 '83e4f0' and esp, 0xfffffff0 + 0x8048388 '50' push eax + 0x8048389 '54' push esp + 0x804838a '52' push edx + 0x804838b '6810860408' push 0x8048610 + 0x8048390 '68a0850408' push 0x80485a0 + 0x8048395 '51' push ecx + 0x8048396 '56' push esi + 0x8048397 '68fd840408' push 0x80484fd + 0x804839c 'e8cfffffff' call *0x8048370 + ────────────────────────────────────────────────────── + + +Note that a :class:`block ` view will show non-transformed instructions' operands +(appart from PC-relative branch offsets which are shown as absolute addresses.) +Block views can be *enhanced* by several analyses that will possibly add symbols related to addresses +(provided by the program's symbol table) or more semantic-related information. These views +are usually available only through the higher level *task* view object and add various +comment tokens to instruction lines. For example:: + + In [7]: print( p.view.codeblock(b) ) + ───────── codeblock 0x8048380 ────────────────────────────────────────── + 0x8048380.text '31ed' xor ebp, ebp + 0x8048382.text '5e' pop esi + 0x8048383.text '89e1' mov ecx, esp + 0x8048385.text '83e4f0' and esp, 0xfffffff0 + 0x8048388.text '50' push eax + 0x8048389.text '54' push esp + 0x804838a.text '52' push edx + 0x804838b.text '6810860408' push 0x8048610<__libc_csu_fini> + 0x8048390.text '68a0850408' push 0x80485a0<__libc_csu_init> + 0x8048395.text '51' push ecx + 0x8048396.text '56' push esi + 0x8048397.text '68fd840408' push 0x80484fd
+ 0x804839c.text 'e8cfffffff' call 0x8048370<__libc_start_main> + ──────────────────────────────────────────────────────────────────────── + Symbolic representations of blocks ================================== @@ -172,39 +202,39 @@ A :class:`node ` object takes a block and allows to get a symbolic functional representation of what this block sequence of instructions is doing:: - In [7]: n = amoco.cfg.node(b) - In [7]: print(n.map.view) - eip <- (eip+-0x10) - eflags: - | cf <- 0x0 - | sf <- (((esp+0x4)&0xfffffff0)<0x0) - | tf <- tf - | zf <- (((esp+0x4)&0xfffffff0)==0x0) - | pf <- (0x6996>>(((esp[0:8]+0x4)&0xf0)>>0x4)[0:4])[0:1] - | of <- 0x0 - | df <- df - | af <- af - ebp <- 0x0 - esp <- (((esp+0x4)&0xfffffff0)-0x24) - esi <- M32(esp) - ecx <- (esp+0x4) - (((esp+0x4)&0xfffffff0)-4) <- eax - (((esp+0x4)&0xfffffff0)-8) <- (((esp+0x4)&0xfffffff0)-0x4) - (((esp+0x4)&0xfffffff0)-12) <- edx - (((esp+0x4)&0xfffffff0)-16) <- 0x8048610 - (((esp+0x4)&0xfffffff0)-20) <- 0x80485a0 - (((esp+0x4)&0xfffffff0)-24) <- (esp+0x4) - (((esp+0x4)&0xfffffff0)-28) <- M32(esp) - (((esp+0x4)&0xfffffff0)-32) <- 0x80484fd - (((esp+0x4)&0xfffffff0)-36) <- (eip+0x21) + In [8]: n = amoco.cfg.node(b) + In [8]: print(n.map.view) + eip ⇽ (eip+-0x10) + eflags: + │ cf ⇽ 0x0 + │ pf ⇽ (0x6996>>(esp+0x4)[4:8])[0:1] + │ af ⇽ af + │ zf ⇽ ({[ 0: 4] -> 0x0, [ 4:32] -> (esp+0x4)[4:32]}==0x0) + │ sf ⇽ ({[ 0: 4] -> 0x0, [ 4:32] -> (esp+0x4)[4:32]}<0x0) + │ tf ⇽ tf + │ df ⇽ df + │ of ⇽ 0x0 + ebp ⇽ 0x0 + esp ⇽ ({[ 0: 4] -> 0x0, [ 4:32] -> (esp+0x4)[4:32]}-0x24) + esi ⇽ M32(esp) + ecx ⇽ (esp+0x4) + ({ | [0:4]->0x0 | [4:32]->(esp+0x4)[4:32] | }-4) ⇽ eax + ({ | [0:4]->0x0 | [4:32]->(esp+0x4)[4:32] | }-8) ⇽ ({[ 0: 4] -> 0x0, [ 4:32] -> (esp+0x4)[4:32]}-0x4) + ({ | [0:4]->0x0 | [4:32]->(esp+0x4)[4:32] | }-12) ⇽ edx + ({ | [0:4]->0x0 | [4:32]->(esp+0x4)[4:32] | }-16) ⇽ 0x8048610 + ({ | [0:4]->0x0 | [4:32]->(esp+0x4)[4:32] | }-20) ⇽ 0x80485a0 + ({ | [0:4]->0x0 | [4:32]->(esp+0x4)[4:32] | }-24) ⇽ (esp+0x4) + ({ | [0:4]->0x0 | [4:32]->(esp+0x4)[4:32] | }-28) ⇽ M32(esp) + ({ | [0:4]->0x0 | [4:32]->(esp+0x4)[4:32] | }-32) ⇽ 0x80484fd + ({ | [0:4]->0x0 | [4:32]->(esp+0x4)[4:32] | }-36) ⇽ (eip+0x21) Here we are with the *map* of the block. -Now what this :class:`mapper ` object says is for example that once the block is executed ``esi`` register -will be set to the 32 bits value pointed by ``esp``, that the carry flag will be 0, or +Now what this :class:`mapper ` object says is for example that once the block +is executed ``esi`` register will be set to the 32 bits value pointed by ``esp``, that the carry flag will be 0, or that the top of the stack will hold value ``eip+0x21``. Rather than extracting the entire view of the mapper we can query any :mod:`expression ` out if it:: - In [8]: print(n.map(p.cpu.ecx)) + In [9]: print(n.map(p.cpu.ecx)) (esp+0x4) There are some caveats when it comes to query memory expressions but we will leave this @@ -212,18 +242,19 @@ for later (see :class:`cas.mapper.mapper`). The ``n.map`` object also provides a better way to see how the memory is modified by the block:: - In [9]: print(n.map.mmap) + In [10]: print(n.map.mmap) - - - - - - - - - > + 0x0 | [4:32]->(esp+0x4)[4:32] | } : + + + + + + + + 0x0 | [4:32]->(esp+0...> + > + The :class:`cas.mapper.mapper` class is an essential part of amoco that captures the semantics of the block by interpreting its' instructions in a symbolic way. Note that it takes no input state @@ -233,15 +264,13 @@ before and even where the block is actually located. For any mapper object, we can get the lists of *input* and *output* expressions, and replace inputs by any chosen expression:: - In [10]: for x in set(n.map.inputs()): print(x) - M32(esp) + In [11]: for x in set(n.map.inputs()): print(x) esp - eax - edx eip + M32(esp) - In [11]: m = n.map.use(eip=0x8048380, esp=0x7fcfffff) - In [12]: print(m.view) + In [12]: m = n.map.use(eip=0x8048380, esp=0x7fcfffff) + In [13]: print(m.view) eip <- 0x8048370 eflags: | cf <- 0x0 @@ -271,15 +300,15 @@ but we are still far from getting the picture of the entire program. In order to reason later about execution paths, we need a way to *chain* block mappers. This is provided by the mapper's shifts operators:: - In [13]: mm = amoco.cas.mapper.mapper() - In [14]: amoco.conf.Cas.noaliasing = True - In [15]: mm[p.cpu.eip] = p.cpu.mem(p.cpu.esp+4,32) - In [16]: print( (m>>mm)(p.cpu.eip) ) + In [14]: mm = amoco.cas.mapper.mapper() + In [15]: amoco.conf.Cas.noaliasing = True + In [16]: mm[p.cpu.eip] = p.cpu.mem(p.cpu.esp+4,32) + In [17]: print( (n.map>>mm)(p.cpu.eip) ) 0x80484fd Here, taking a new mapper as if it came either from a block or a stub, and assuming -that there is no memory aliasing, the sequential execution of ``m`` followed by ``mm`` -would branch to address ``0x80484fd`` (``#main``). +that there is no memory aliasing, the sequential execution of ``n.map`` followed by ``mm`` +would branch to address ``0x80484fd`` (``
``). Starting some analysis ====================== diff --git a/setup.py b/setup.py index d469936..87d5dd8 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ setup( name = 'amoco', - version = '2.9.2', + version = '2.9.3', description = 'yet another binary analysis framework', long_description = long_descr, # Metadata diff --git a/tests/samples/test.c b/tests/samples/test.c new file mode 100644 index 0000000..4f79ce3 --- /dev/null +++ b/tests/samples/test.c @@ -0,0 +1,21 @@ +// Partial RELRO: +// gcc -m32 -no-pie -g -Wl,-z,relro -o x86/test_partial.elf test.c +// gcc -no-pie -g -Wl,-z,relro -o x64/test_partial.elf64 test.c +// +// Full RELRO: +// gcc -m32 -g -Wl,-z,relro,-z,now -o x86/test_full.elf test.c +// gcc -g -Wl,-z,relro,-z,now -o x64/test_full.elf64 test.c +#include +#include + + +int main(int argc, int *argv[]) +{ +size_t *p = (size_t *) strtol(argv[1], NULL, 16); + +p[0] = 0xDEADBEEF; + +printf("RELRO: %p\n", p); + +return 0; +} diff --git a/tests/samples/x64/test_full.elf64 b/tests/samples/x64/test_full.elf64 new file mode 100644 index 0000000000000000000000000000000000000000..7f7183c207f4d7a9f70d3f9b3df6eda343e4e3f3 GIT binary patch literal 10920 zcmeHNYj7J^6}~IUwk+GSVkfDS6sm>>62Ow3hvTG8V>xjuGtQ%FQd(%b)njF=k+nj* z3Ql=>NLnI7AZ36luc5#Ugh!b$3=keeT@p$QGfc~~JSQ*|Qie{=Kue$`<~w)qu~%!E z#|*!i-WlyV=ey^ed+)h-_ny70w+xN!@M)Uh;uqHok|rA*WI$D{Ua1(60nsm-aa}2{ z7LAYt5_9SY9D-V>TvV=^1{LlHCA)r~19<6`N{=b`kf`dK(x@ems0Nq{5tRyLM}0lr zCPmPwWSHKe`Z1;`+mT3iQ_5~i*)g3~9b?Mlqp{I%pYrFW+fhe_Dd|$#^Q4_6ezHl5 z^XPRBESR$0jj*GB{--6P6!)t37K+Or)qkdHxFTN2r~B5&3)${MzF3*)p6KuE?pq(T z%-C8vZt_pMH|*LkTcY{q&{Cfl<3||PpF7r;d(b?043*c$s(K`C%qn@AFEug;)_{utd1mc&WaP^5&IbXDM!m`V@SrEFO znMmomd@)tX-(^4~McuMfWm_Ll<%{Bmk>SB@`r6pq*ao2w@7t+ojj}PCw``-lZ|Amx zSv2;g(gm^{9XE^0Qg>AAD$3sZy=BWF*YpFvr5 zC&@Ilryi5?TOsF8I;{!O`}A=2@!ONt-z1Oxc6!ggkrNHyfo}3d+f9;e`5hEzmh{2q zFYQ-trrtJBGcE^JL|4<{eLYv(1syjNaFHv!$%=&)SXG{*q_hBCB{SQ2cF77gE&MvT7Ay);~XZe$JhBA9&WlK(;zHFfvXcC`2B zJpPgD-$$y?ZLdC)m|6Ubv>f1tJS+5?H(5(g5uC-k!?=NkO;S@fFx$<;z@ z+$Z)9jqKgMIl8J8g1o|iEj`O=%}!mKnK=eJ4SE_>p!r)sLWs^Ipd|_>D6Oj=b+gcO zl!|uOUZG7yv=yyQ!IN53gmBtB?}x8fkb)}p^@X?w^`zee{T|W{M|Om}Ue(_6`rxG4 zy8POgue(M*#>gJiCir}Y@*~>E~$@i&;FXu=TDoNwq>x`<$7AMPbMOzf@Qgn%;OgPs&t@1E_ z#>+hkO1yum;fO1g;l+T%Fy60Z?)Q|^LlP?sxBp@#v)=PF&iD{l31OR&p_fu^b}e#3-nBqrUa~xR~%z&(KH9&i6z= zuKr-t`-o`4Da73GYuZF4(DXqGHhxor4c`L^1RC%n1%rkM$QnQ4e@C9!OQdvy*oI(Z z@SRklH9iWkiTL1$!95Xpb~#L%zY8GnTpCx=Oalu%e+<`PGo_z_e-IyOZh~Uq67gNl z?w$Fs<6)3Py_H8OOL^T^U8|0j5c{f#kAb1JBy8`#q zAe;Y6&_{_C&FhH&7!5br{0ZVekp>(QjlrvdH3mNbsg)*@y4^-=hESSKi5_LNQ-s4X zZ?BM#ts3F&zbAgc;ceIj5|VEWz86B%15k+sq0;a+YCcjV_LyXML2jr@_T@kum#1OS z`URlR;I~LL{T3wh8LC=A2v+4OXkazp3)1l(Kw4`WtatfqA9^+Jl+HV66@2&J*12Cw zRN=dK&*GBM!WqerVW0=VH3rF5V~}nvp)Jx@g*#jP;T7RUT1Qg^k4WEA;SXwK@6rHD0~nht)`a>BEe40A8u(OTMc4!+pMF0Xor;8Fx*LE3payM zXxeZmS+rdZKXFusJ7Ibq_Mi6kjt>}ZT}D7e0ch6&7F~^Dbi}l+%bZ@nY}s%p4P~I~ zeS^!$X=lT1H+*=KFNTAlGdvQ-5AgGeFHkJB=fiEd$`ejZh8C|_Wjecxk7hD)99rpp z>(vR?Xt5IC7|$0og-X^CQcnr<_0oCUs!f6~`*6vyz+(Q; zXx?;KXh;?_XC1b5qg*cF{Klz6jD5T&{H@xP!M}Vxc<5H24};KkGndnC9OdlR%SI}z zm+Z38Gh=irWZ|%gs;n`yY^N(ZI#`Qt>+N-(*3s^9yHYf^j2cCwoX8!{*qRg4tTu_zWYDAAfelC_mOWHeO1H zkDFKlXvM5J?WzFei_Z9+c4TMag-q3?P&Mp*&M#~6khoaaPmADuyjH(e4AjZj{Gv^i>f+(q?=rQ1+Gq3oT0A03oDjPz z{MeuP`+^Hblsm*!UH#74_k~*h#e&}xYVjrbUJ`XflJ|?Hg7bQJrJu5Rbv|5+cZo<{ zoZn*H+Su3xzrA>Ie#`OV{C4BTUsMYy$uIbAVXhj_c&x%d&CDSVNAF=i5f-OC^N3AX z;?o|Sc0AJO_d3Um5F5d0VlEEU9u>;uG`tOY<3A3Z{P24s=~6k2pC;zd<9)#E^?#vp zeMI`{5a*CGcuPG$l!{}&p4U&xr!_HuoqSvA^ZTh3arv2K=C9kQfopZvl^+A>KpgH+ z>zN-1oxr2gSMBAX#OJTWIPeJ8r*|E;0q+p=@4G&U*ZL;uRSV#G<=?yh4*`$PcO&jz zz|Z@EFY;X>_&ttIDPdlO5%6<>A5UMDe!^l}y|x+3;47dJthX-DeP=3t{x19qrT;D9 zr0vaqSN}J_qsph7`~&z(H=y_jfp>^H&(%!Xw(LqShg@v#!lykqhD1Tnn)+zLOs5KZ7Kx6frz#U7W72kJ8`;>#zD@o0mFS~fUQdsucO`ZXZ>z15AO8R$ zrPGI!%0qJdUZLMGvU@NwqVL|Z<(jh3yPlg$*B72O@{a<_}G_ol>r9MFp z4AgvE^nQ8tR+FXkc@a(4d~0+*WzN&LOnod>%+j|>%DyOz(vks3V%>u=DR!PU0LzZfkOCnZ8?i))C4tLwBQ6=G}AeOG= z@tHfH6)}>=Qr4J=We*qO&!I@dWz8X@Y~{^jji95hY!p&tptw@O7BM-@7}(gT3DKqu zFGlMNN~WBWm@%eS)L0fySaM9ArR7*Kj}}tn`3(9*Ga5q#MGVV&97`3Lk*-(Vh5s&oJSw-D{XxMi_7Ife5-rrw>Hblv485a?0P_M9g& zjk=Y=^K*{jUNCNZ&L^2(!-|S}?Y9Ge4rhDZKIffGx&OSad$-43u*XaAZ2!+FM@*+s zM};28GM9S$oc7nI0!4SMkR0cGS_?CHJ56U8WIb%l&6PrUy|^XQ3=} zUd%MA?7jV0u(%5h#gy$ie`Y$R6uEux_>U_4L8W(5O%PK)A13>y_<8N=e28MAw!7JU zobz(*{53_npT(-+-1eNeGv)T#faA&ZJsx|`@0oIc*`8&lANJVuKFpN%Pj}&V8T&XG zcmMf&08>5}XMOMZKc(z>{y3jt+RlcGdhI{&v7c0V8Pn_jjs1fj`zdA5luqf~h2zH9 zqhKiarhAAA=a>AyC$B!sOn(F$8YA0t-hHNxyv?mQN1Bt_{@19Wwa51Sel(@*`Tt|Q zKG}}vk@9pJBHQzO)M*v?J*aaRwrBbWRJ!eF^G($Nx5GZzj^$}EgmL?vzeSXxSKl)o zf)0}j>%q@m*nW*01kd(NFS{_e>L%5`*Du?#F6HZ9jOy~r{!Sj4GVtmyTfqJYIE|#@ z^B8zU@v;T%f22NW{lsxId-dAWAvj^)aej7*V{(dbOyz;?-PEHZHE&TH%!FEaQJ~ZY p&nNFc^nCDy2#64msaN7Ik8^HUD&evSzk2o;qK?M(9s`dk{t1Pw{}uoM literal 0 HcmV?d00001 diff --git a/tests/samples/x64/test_partial.elf64 b/tests/samples/x64/test_partial.elf64 new file mode 100644 index 0000000000000000000000000000000000000000..00453cbd468f2c72e928185297ebc260b55bb731 GIT binary patch literal 10864 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 literal 0 HcmV?d00001 diff --git a/tests/samples/x86/test_partial.elf b/tests/samples/x86/test_partial.elf new file mode 100644 index 0000000000000000000000000000000000000000..ea7c37b0b3d9d33fdd87cd3cf118d90284e905a4 GIT binary patch literal 9564 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 literal 0 HcmV?d00001 diff --git a/tests/samples/x86/test_pie.elf b/tests/samples/x86/test_pie.elf new file mode 100644 index 0000000000000000000000000000000000000000..60adeb01a2df0c2d9788ef26fb302c45743cdce3 GIT binary patch literal 9604 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 F{U5