mirror of
https://github.com/reswitched/CageTheUnicorn.git
synced 2024-06-16 03:17:01 -04:00
Initial public commit.
This commit is contained in:
commit
bd045eeea7
22
bundler.py
Normal file
22
bundler.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
import struct
|
||||
from glob import glob
|
||||
|
||||
mainaddr = raw_input('Enter main module address: ')
|
||||
if mainaddr.startswith('0x'):
|
||||
mainaddr = mainaddr[2:]
|
||||
mainaddr = int(mainaddr, 16)
|
||||
print 'Main at 0x%016x' % mainaddr
|
||||
wkcaddr = raw_input('Enter wkc module address: ')
|
||||
if wkcaddr.startswith('0x'):
|
||||
wkcaddr = wkcaddr[2:]
|
||||
wkcaddr = int(wkcaddr, 16)
|
||||
print 'WKC at 0x%016x' % wkcaddr
|
||||
|
||||
with file('membundle.bin', 'wb') as fp:
|
||||
files = glob('memdumps/*.bin')
|
||||
fp.write(struct.pack('<IQQ', len(files), mainaddr, wkcaddr))
|
||||
for fn in files:
|
||||
addr = int(fn[11:].split(' ')[0], 16)
|
||||
data = file(fn, 'rb').read()
|
||||
fp.write(struct.pack('<QI', addr, len(data)))
|
||||
fp.write(data)
|
291
ceval.py
Normal file
291
ceval.py
Normal file
|
@ -0,0 +1,291 @@
|
|||
from pycparser.c_parser import CParser
|
||||
from pycparser.c_ast import *
|
||||
import struct
|
||||
|
||||
class AstTranslator(object):
|
||||
def process(self, ast):
|
||||
node = ast.__class__.__name__
|
||||
|
||||
if hasattr(self, node):
|
||||
func = getattr(self, node)
|
||||
fcode = func.im_func.func_code
|
||||
argnames = fcode.co_varnames[1:fcode.co_argcount]
|
||||
args = [getattr(ast, name) for name in argnames]
|
||||
return func(*args)
|
||||
else:
|
||||
print 'Unhandled AST node:'
|
||||
ast.show()
|
||||
return '?unknown?'
|
||||
|
||||
def UnaryOp(self, op, expr):
|
||||
opmap = {'*' : 'deref', '-' : 'neg', '!' : 'not', '~' : 'bnot'}
|
||||
return opmap[op], self.process(expr)
|
||||
|
||||
def BinaryOp(self, op, left, right):
|
||||
return op, self.process(left), self.process(right)
|
||||
|
||||
def Cast(self, to_type, expr):
|
||||
return 'cast', self.process(to_type), self.process(expr)
|
||||
|
||||
def ID(self, name):
|
||||
return 'register', name
|
||||
|
||||
def Typename(self, type):
|
||||
return self.process(type)
|
||||
|
||||
def TypeDecl(self, type):
|
||||
return self.process(type)
|
||||
|
||||
def IdentifierType(self, names):
|
||||
return ' '.join(names)
|
||||
|
||||
def PtrDecl(self, type):
|
||||
return 'ptr', self.process(type)
|
||||
|
||||
def ArrayRef(self, name, subscript):
|
||||
return '[]', self.process(name), self.process(subscript)
|
||||
|
||||
def Constant(self, type, value):
|
||||
if type == 'int':
|
||||
if value.startswith('0x'):
|
||||
return int(value[2:], 16)
|
||||
elif value.startswith('0b'):
|
||||
return int(value[2:], 2)
|
||||
elif value.startswith('0'):
|
||||
return int(value, 8)
|
||||
else:
|
||||
return int(value)
|
||||
elif type == 'float':
|
||||
return float(value)
|
||||
else:
|
||||
print 'Unknown constant type:', type, `value`
|
||||
return '?unkconst?'
|
||||
|
||||
def Assignment(self, op, lvalue, rvalue):
|
||||
lvalue = self.process(lvalue)
|
||||
rvalue = self.process(rvalue)
|
||||
|
||||
if op != '=':
|
||||
rvalue = op[0], lvalue, rvalue
|
||||
|
||||
return '=', lvalue, rvalue
|
||||
|
||||
dispatchers = {}
|
||||
|
||||
def ddp(name):
|
||||
def sub(func):
|
||||
dispatchers[name] = func
|
||||
return func
|
||||
if callable(name):
|
||||
dispatchers[name.func_name] = name
|
||||
return name
|
||||
return sub
|
||||
|
||||
class Register(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
class TypedValue(object):
|
||||
def __init__(self, type, value):
|
||||
self.type, self.value = type, value
|
||||
while isinstance(self.value, TypedValue):
|
||||
self.value = self.value.value
|
||||
|
||||
def __repr__(self):
|
||||
return '%s:%r' % (self.type, self.value)
|
||||
|
||||
@property
|
||||
def stride(self):
|
||||
return int(self.type[1:].rstrip('*')) / 8
|
||||
|
||||
@property
|
||||
def pointer(self):
|
||||
return '*' in self.type
|
||||
|
||||
def bare(value):
|
||||
if isinstance(value, TypedValue):
|
||||
return value.value
|
||||
else:
|
||||
return value
|
||||
|
||||
def autotype(value):
|
||||
if isinstance(value, TypedValue):
|
||||
return value
|
||||
elif isinstance(value, float):
|
||||
return TypedValue('f64', value)
|
||||
elif isinstance(value, str):
|
||||
return value
|
||||
else:
|
||||
return TypedValue('i64', value)
|
||||
|
||||
class SexpRunner(object):
|
||||
def __init__(self, ctu):
|
||||
self.ctu = ctu
|
||||
|
||||
def run(self, sexp, rvalue=None):
|
||||
if not isinstance(sexp, tuple):
|
||||
return autotype(sexp)
|
||||
|
||||
if sexp[0] in dispatchers:
|
||||
if rvalue is None:
|
||||
return dispatchers[sexp[0]](self, *sexp[1:])
|
||||
else:
|
||||
return dispatchers[sexp[0]](self, *tuple(list(sexp[1:]) + [rvalue]))
|
||||
else:
|
||||
print 'Unhandled S-exp:', sexp
|
||||
return None
|
||||
|
||||
@ddp('=')
|
||||
def assign(self, left, right):
|
||||
return self.run(left, self.run(right))
|
||||
|
||||
@ddp('[]')
|
||||
def subscript(self, base, sub, ass=None):
|
||||
base, sub = self.run(base), self.run(sub)
|
||||
|
||||
addr = bare(base) + bare(sub) * base.stride
|
||||
|
||||
return self.deref(TypedValue(base.type, addr), ass)
|
||||
|
||||
@ddp
|
||||
def deref(self, ptr, ass=None):
|
||||
ptr = self.run(ptr)
|
||||
|
||||
assert ptr.pointer
|
||||
|
||||
fmtmap = dict(u8='B', i8='b', u16='H', i16='h', u32='I', i32='i', u64='L', i64='l', f32='f', f64='d')
|
||||
fmt = fmtmap[ptr.type.rstrip('*')]
|
||||
size = struct.calcsize(fmt)
|
||||
|
||||
if ass is None:
|
||||
data = self.ctu.readmem(bare(ptr), size)
|
||||
return TypedValue(ptr.type[:-1], struct.unpack(fmt, data)[0])
|
||||
else:
|
||||
self.ctu.writemem(bare(ptr), struct.pack(fmt, bare(ass)))
|
||||
|
||||
@ddp
|
||||
def register(self, name, ass=None):
|
||||
name = name.upper()
|
||||
if name == 'PC':
|
||||
if ass is None:
|
||||
return TypedValue('u64', self.ctu.pc)
|
||||
else:
|
||||
self.ctu.pc = bare(ass)
|
||||
else:
|
||||
typemap = dict(X='u64', W='u32', D='f64', Q='f128')
|
||||
assert name[0] in 'XW' # XXX: Add float support
|
||||
if ass is None:
|
||||
type = typemap[name[0]]
|
||||
value = self.ctu.reg(int(name[1:]))
|
||||
if type == 'u32':
|
||||
value &= 0xFFFFFFFF
|
||||
return TypedValue(type, value)
|
||||
else:
|
||||
self.ctu.reg(int(name[1:]), bare(ass))
|
||||
|
||||
@ddp
|
||||
def cast(self, type, value):
|
||||
return TypedValue(self.run(type), self.run(value))
|
||||
|
||||
@ddp
|
||||
def ptr(self, type):
|
||||
return self.run(type) + '*'
|
||||
|
||||
@ddp('+')
|
||||
def add(self, a, b):
|
||||
a, b = self.run(a), self.run(b)
|
||||
if b.pointer and not a.pointer:
|
||||
a, b = b, a
|
||||
if a.pointer and not b.pointer:
|
||||
return TypedValue(a.type, bare(a) + bare(b) * a.stride)
|
||||
return TypedValue(a.type, bare(a) + bare(b))
|
||||
|
||||
@ddp('-')
|
||||
def sub(self, a, b):
|
||||
a, b = self.run(a), self.run(b)
|
||||
if b.pointer and not a.pointer:
|
||||
return TypedValue(b.type, bare(a) * b.stride - bare(b))
|
||||
elif a.pointer and not b.pointer:
|
||||
return TypedValue(a.type, bare(a) - bare(b) * a.stride)
|
||||
return TypedValue(a.type, bare(a) + bare(b))
|
||||
|
||||
@ddp('*')
|
||||
def mul(self, a, b):
|
||||
a, b = self.run(a), self.run(b)
|
||||
return TypedValue(a.type, bare(a) * bare(b))
|
||||
|
||||
@ddp('/')
|
||||
def div(self, a, b):
|
||||
a, b = self.run(a), self.run(b)
|
||||
return TypedValue(a.type, bare(a) / bare(b))
|
||||
|
||||
@ddp('==')
|
||||
def eq(self, a, b):
|
||||
a, b = self.run(a), self.run(b)
|
||||
return TypedValue('i64', 1 if a.value == b.value else 0)
|
||||
|
||||
@ddp('!=')
|
||||
def ne(self, a, b):
|
||||
a, b = self.run(a), self.run(b)
|
||||
return TypedValue('i64', 1 if a.value != b.value else 0)
|
||||
|
||||
@ddp('>')
|
||||
def gt(self, a, b):
|
||||
a, b = self.run(a), self.run(b)
|
||||
return TypedValue('i64', 1 if a.value > b.value else 0)
|
||||
|
||||
@ddp('>=')
|
||||
def ge(self, a, b):
|
||||
a, b = self.run(a), self.run(b)
|
||||
return TypedValue('i64', 1 if a.value >= b.value else 0)
|
||||
|
||||
@ddp('<')
|
||||
def lt(self, a, b):
|
||||
a, b = self.run(a), self.run(b)
|
||||
return TypedValue('i64', 1 if a.value < b.value else 0)
|
||||
|
||||
@ddp('<=')
|
||||
def le(self, a, b):
|
||||
a, b = self.run(a), self.run(b)
|
||||
return TypedValue('i64', 1 if a.value <= b.value else 0)
|
||||
|
||||
@ddp('&&')
|
||||
def booland(self, a, b):
|
||||
a, b = self.run(a), self.run(b)
|
||||
return TypedValue('i64', 1 if bool(a.value) and bool(b.value) else 0)
|
||||
|
||||
@ddp('||')
|
||||
def boolor(self, a, b):
|
||||
a, b = self.run(a), self.run(b)
|
||||
return TypedValue('i64', 1 if bool(a.value) or bool(b.value) else 0)
|
||||
|
||||
def compile(code):
|
||||
parser = CParser()
|
||||
|
||||
stypes = 'u8 i8 u16 i16 u32 i32 u64 i64 f32 f64 f128'
|
||||
code = 'void runner() { ' + code + ' ; }'
|
||||
for type in stypes.split(' '):
|
||||
code = 'typedef void %s; %s' % (type, code)
|
||||
|
||||
ast = parser.parse(code)
|
||||
found = None
|
||||
for _, child in ast.children():
|
||||
if isinstance(child, FuncDef):
|
||||
found = child
|
||||
break
|
||||
|
||||
assert found is not None
|
||||
assert len(found.body.children()) == 1
|
||||
|
||||
ast = found.body.children()[0][1]
|
||||
sexp = AstTranslator().process(ast)
|
||||
|
||||
def run(ctu):
|
||||
return bare(SexpRunner(ctu).run(sexp))
|
||||
return run
|
||||
|
||||
def ceval(code, ctu):
|
||||
return compile(code)(ctu)
|
617
ctu.py
Normal file
617
ctu.py
Normal file
|
@ -0,0 +1,617 @@
|
|||
import gzip, math, os, re, struct, sys
|
||||
from cmd import Cmd
|
||||
|
||||
import colorama
|
||||
from colorama import Fore, Back, Style
|
||||
|
||||
from unicorn import *
|
||||
from unicorn.arm64_const import *
|
||||
|
||||
from capstone import *
|
||||
|
||||
from ceval import ceval, compile
|
||||
from util import *
|
||||
from svc import SvcHandler
|
||||
|
||||
TRACE_NONE = 0
|
||||
TRACE_INSTRUCTION = 1
|
||||
TRACE_BLOCK = 2
|
||||
TRACE_FUNCTION = 4
|
||||
|
||||
def colorDepth(depth):
|
||||
colors = [Fore.RED, Fore.WHITE, Fore.GREEN, Fore.YELLOW, Style.BRIGHT + Fore.BLUE, Fore.MAGENTA, Fore.CYAN]
|
||||
|
||||
return colors[depth % len(colors)]
|
||||
|
||||
class CTU(Cmd, object):
|
||||
def __init__(self, flags=0):
|
||||
Cmd.__init__(self)
|
||||
|
||||
colorama.init()
|
||||
self.initialized = False
|
||||
|
||||
self.flags = 0
|
||||
self.sublevel = 0
|
||||
self.breakpoints = set()
|
||||
self.watchpoints = []
|
||||
|
||||
self.mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
|
||||
self.md = Cs(CS_ARCH_ARM64, CS_MODE_ARM)
|
||||
|
||||
self.mu.hook_add(UC_HOOK_CODE, self.hook_insn_bytes)
|
||||
self.mu.hook_add(UC_HOOK_BLOCK, self.trace_block)
|
||||
|
||||
self.insnhooks = {}
|
||||
|
||||
self.termaddr = 1 << 61 # Pseudoaddress upon which to terminate execution
|
||||
self.mu.mem_map(self.termaddr, 0x1000)
|
||||
self.mu.mem_write(self.termaddr, '\x1F\x20\x03\xD5') # NOP
|
||||
|
||||
self.stacktop = 1 << 48
|
||||
self.stacksize = 8 * 1024 * 1024 # 8MB
|
||||
self.mu.mem_map(self.stacktop - self.stacksize, self.stacksize)
|
||||
|
||||
self.heapbase = 1 << 50
|
||||
self.heapsize = 32 * 1024 * 1024 # 32MB
|
||||
self.heapoff = 0
|
||||
self.mu.mem_map(self.heapbase, self.heapsize)
|
||||
|
||||
self.tlsbase = 1 << 49
|
||||
self.tlssize = 1024 * 1024 # 1MB
|
||||
self.mu.mem_map(self.tlsbase, self.tlssize)
|
||||
|
||||
for i in xrange(30):
|
||||
self.hookinsn(0xD53BD060 + i, (lambda i: lambda _, __: self.tlshook(i))(i))
|
||||
|
||||
self.svch = SvcHandler(self)
|
||||
|
||||
self.reset()
|
||||
self.enableFP()
|
||||
|
||||
self.execfunc = None
|
||||
self.initialized = True
|
||||
|
||||
def reset(self):
|
||||
self.debugging = False
|
||||
self.started = False
|
||||
self.restarting = False
|
||||
self.singlestep = False
|
||||
|
||||
self.skipbp = False
|
||||
self.lastinsn = None
|
||||
self.blx = False
|
||||
self.callstack = []
|
||||
|
||||
self.loadmemory()
|
||||
if not self.initialized:
|
||||
mapLoader('main.map', MainAddress, 0x19e5006000)
|
||||
mapLoader('webkit_wkc.map', WKCAddress, 0x3C0D91E000)
|
||||
|
||||
self.heapoff = 0
|
||||
self.mu.mem_write(self.heapbase, '\0' * self.heapsize)
|
||||
self.mu.mem_write(self.stacktop - self.stacksize, '\0' * self.stacksize)
|
||||
|
||||
self.mu.mem_write(self.tlsbase + 0x1F8, struct.pack('<Q', 0x5AF9C2D000 + 0xFD3840))
|
||||
|
||||
for i in xrange(32):
|
||||
self.reg(i, 0)
|
||||
|
||||
def setup(self, func):
|
||||
self.execfunc = func
|
||||
|
||||
def run(self, flags=0):
|
||||
self.reset()
|
||||
assert self.execfunc is not None
|
||||
self.flags = flags
|
||||
self.execfunc(self)
|
||||
|
||||
def enableFP(self):
|
||||
addr = 1 << 62
|
||||
self.mu.mem_map(addr, 0x1000)
|
||||
self.mu.mem_write(addr, '\x41\x10\x38\xd5\x00\x00\x01\xaa\x40\x10\x18\xd5\x40\x10\x38\xd5\xc0\x03\x5f\xd6')
|
||||
assert (self.call(addr, 3 << 20) >> 20) & 3 == 3
|
||||
|
||||
def loadmemory(self):
|
||||
if not os.path.isfile('membundle.bin'):
|
||||
with gzip.GzipFile('membundle.bin.gz', 'rb') as ifp:
|
||||
with file('membundle.bin', 'wb') as ofp:
|
||||
print 'Decompressing membundle'
|
||||
ofp.write(ifp.read())
|
||||
print 'Done!'
|
||||
|
||||
with file('membundle.bin', 'rb') as fp:
|
||||
regions, mainbase, wkcbase = struct.unpack('<IQQ', fp.read(20))
|
||||
if not self.initialized:
|
||||
rmap = []
|
||||
for i in xrange(regions):
|
||||
addr, dlen = struct.unpack('<QI', fp.read(12))
|
||||
data = fp.read(dlen)
|
||||
if not self.initialized:
|
||||
self.mu.mem_map(addr, dlen)
|
||||
rmap.append((addr, dlen))
|
||||
self.mu.mem_write(addr, data)
|
||||
|
||||
if not self.initialized:
|
||||
MainAddress.realbase = mainbase
|
||||
WKCAddress.realbase = wkcbase
|
||||
MainAddress.realsize = WKCAddress.realsize = 0
|
||||
inMain = inWKC = False
|
||||
last = 0
|
||||
rmap.sort(key=lambda x: x[0])
|
||||
for (addr, dlen) in rmap:
|
||||
if addr == mainbase:
|
||||
inMain = True
|
||||
last = addr
|
||||
elif addr == wkcbase:
|
||||
inWKC = True
|
||||
last = addr
|
||||
|
||||
if (inMain or inWKC) and last != addr:
|
||||
inMain = inWKC = False
|
||||
elif inMain:
|
||||
MainAddress.realsize += dlen
|
||||
last = addr + dlen
|
||||
elif inWKC:
|
||||
WKCAddress.realsize += dlen
|
||||
last = addr + dlen
|
||||
|
||||
def trace_insn(self, mu, addr, size, user_data):
|
||||
if not self.initialized:
|
||||
return
|
||||
for ins in self.md.disasm(str(mu.mem_read(addr, size)), addr):
|
||||
print("0x%08x: %s %s" % (ins.address, ins.mnemonic, ins.op_str))
|
||||
|
||||
def trace_block(self, mu, addr, size, user_data):
|
||||
if not self.initialized or (self.flags & TRACE_BLOCK) == 0:
|
||||
return
|
||||
print 'Block at %s - %s' % (raw(addr, pad=True), raw(addr + size, pad=True))
|
||||
if addr == MainAddress(0x1ec928) or addr == MainAddress(0x1ec9e0) or addr == MainAddress(0x1ecab4):
|
||||
print '\nFATAL:\n%s\n%s\n%s\n' % (
|
||||
self.readmem(self.reg(0), 0x100).split('\0', 1)[0],
|
||||
self.readmem(self.reg(1), 0x100).split('\0', 1)[0],
|
||||
self.readmem(self.reg(2), 0x100).split('\0', 1)[0]
|
||||
)
|
||||
self.stop()
|
||||
|
||||
def trace_func(self, mu, addr, size, user_data):
|
||||
if not self.initialized:
|
||||
return
|
||||
if self.blx:
|
||||
self.callstack.append(addr)
|
||||
print ' ' * len(self.callstack) + colorDepth(len(self.callstack)) + '-> %s' % raw(addr), Style.RESET_ALL
|
||||
self.blx = False
|
||||
insn, = struct.unpack('<I', self.mu.mem_read(addr, 4))
|
||||
|
||||
bl_mask = 0b11101100 << 24
|
||||
bl_match = 0b10000100 << 24
|
||||
|
||||
blr_mask = 0b011011111010 << 20
|
||||
blr_match = 0b010001100010 << 20
|
||||
|
||||
ret_mask = 0b011011110110 << 20
|
||||
ret_match = 0b010001100100 << 20
|
||||
|
||||
if (insn & bl_mask) == bl_match or (insn & blr_mask) == blr_match:
|
||||
self.blx = True
|
||||
elif (insn & ret_mask) == ret_match:
|
||||
print ' ' * len(self.callstack) + colorDepth(len(self.callstack)) + '<- %s' % raw(self.callstack.pop()), Style.RESET_ALL
|
||||
|
||||
def hook_insn_bytes(self, mu, addr, size, user_data):
|
||||
if self.restarting:
|
||||
return
|
||||
|
||||
if self.skipbp and not self.singlestep:
|
||||
self.skipbp = False
|
||||
elif self.singlestep or addr in self.breakpoints:
|
||||
if self.singlestep:
|
||||
self.singlestep = False
|
||||
else:
|
||||
print 'Breakpoint at %s' % raw(addr)
|
||||
self.skipbp = True
|
||||
self.debugbreak()
|
||||
else:
|
||||
for code, func in self.watchpoints:
|
||||
if func(self):
|
||||
print 'Watchpoint %s triggered at %s' % (code, raw(addr))
|
||||
self.skipbp = True
|
||||
self.debugbreak()
|
||||
break
|
||||
|
||||
if self.flags & TRACE_INSTRUCTION and self.flags & TRACE_FUNCTION:
|
||||
if self.blx:
|
||||
self.trace_func(mu, addr, size, user_data)
|
||||
self.trace_insn(mu, addr, size, user_data)
|
||||
else:
|
||||
self.trace_insn(mu, addr, size, user_data)
|
||||
self.trace_func(mu, addr, size, user_data)
|
||||
elif self.flags & TRACE_INSTRUCTION:
|
||||
self.trace_insn(mu, addr, size, user_data)
|
||||
elif self.flags & TRACE_FUNCTION:
|
||||
self.trace_func(mu, addr, size, user_data)
|
||||
|
||||
self.lastinsn = addr
|
||||
|
||||
insn, = struct.unpack('<I', self.mu.mem_read(addr, 4))
|
||||
|
||||
if insn in self.insnhooks:
|
||||
if self.insnhooks[insn](self, addr) == False:
|
||||
self.pc += 4
|
||||
|
||||
def hookinsn(self, insn, func=None):
|
||||
def sub(func):
|
||||
assert insn not in self.insnhooks
|
||||
self.insnhooks[insn] = func
|
||||
if func is None:
|
||||
return sub
|
||||
sub(func)
|
||||
|
||||
def tlshook(self, reg):
|
||||
self.reg(reg, self.tlsbase)
|
||||
return False
|
||||
|
||||
def call(self, pc, *args):
|
||||
self.started = True
|
||||
|
||||
self.callstack.append(pc)
|
||||
|
||||
self.mu.reg_write(UC_ARM64_REG_X30, self.termaddr)
|
||||
self.mu.reg_write(UC_ARM64_REG_SP, self.stacktop)
|
||||
|
||||
for i, v in enumerate(args):
|
||||
self.mu.reg_write(UC_ARM64_REG_X0 + i, v)
|
||||
|
||||
try:
|
||||
self.mu.emu_start(native(pc), self.termaddr + 4)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
print 'Exception at %s' % raw(self.lastinsn)
|
||||
self.dumpregs()
|
||||
|
||||
self.started = False
|
||||
|
||||
if self.restarting:
|
||||
self.restarting = False
|
||||
raise Restart()
|
||||
|
||||
return self.mu.reg_read(UC_ARM64_REG_X0)
|
||||
|
||||
def stop(self):
|
||||
self.mu.reg_write(UC_ARM64_REG_PC, self.termaddr)
|
||||
|
||||
def malloc(self, size):
|
||||
self.heapoff += size
|
||||
assert self.heapoff <= self.heapsize
|
||||
return self.heapbase + self.heapoff - size
|
||||
|
||||
def writemem(self, addr, data):
|
||||
try:
|
||||
self.mu.mem_write(addr, data)
|
||||
return True
|
||||
except unicorn.UcError:
|
||||
return False
|
||||
|
||||
def readmem(self, addr, size):
|
||||
try:
|
||||
return self.mu.mem_read(native(addr), size)
|
||||
except unicorn.UcError:
|
||||
return None
|
||||
|
||||
def reg(self, i, val=None):
|
||||
sr = {'LR': 30, 'SP': 31}
|
||||
for ri in xrange(32):
|
||||
sr['X%i' % ri] = ri
|
||||
|
||||
if isinstance(i, str) and i.upper() in sr:
|
||||
i = sr[i.upper()]
|
||||
|
||||
if i <= 28:
|
||||
c = UC_ARM64_REG_X0 + i
|
||||
elif i == 29 or i == 30:
|
||||
c = UC_ARM64_REG_X29 + i - 29
|
||||
elif i == 31:
|
||||
c = UC_ARM64_REG_SP
|
||||
else:
|
||||
return None
|
||||
|
||||
if val is None:
|
||||
return self.mu.reg_read(c)
|
||||
else:
|
||||
self.mu.reg_write(c, native(val))
|
||||
return True
|
||||
|
||||
@property
|
||||
def pc(self):
|
||||
return self.mu.reg_read(UC_ARM64_REG_PC)
|
||||
|
||||
@pc.setter
|
||||
def pc(self, val):
|
||||
self.mu.reg_write(UC_ARM64_REG_PC, val)
|
||||
|
||||
def dumpregs(self):
|
||||
sr = {30: 'LR', 31: 'SP'}
|
||||
print '-' * 52
|
||||
for i in xrange(0, 32, 2):
|
||||
an = sr[i] if i in sr else 'X%i' % i
|
||||
bn = sr[i + 1] if i + 1 in sr else 'X%i' % (i + 1)
|
||||
an += ' ' * (3 - len(an))
|
||||
bn += ' ' * (3 - len(bn))
|
||||
print '%s - 0x%016x %s - 0x%016x' % (
|
||||
an, self.reg(i),
|
||||
bn, self.reg(i + 1)
|
||||
)
|
||||
print '-' * 52
|
||||
print
|
||||
|
||||
def dumpmem(self, addr, size):
|
||||
addr = native(addr)
|
||||
data = self.readmem(addr, size)
|
||||
if data is None:
|
||||
print 'Unmapped memory at %s' % raw(addr)
|
||||
return
|
||||
|
||||
fmt = '%%0%ix |' % (int(math.log(addr + size, 16)) + 1)
|
||||
for i in xrange(0, len(data), 16):
|
||||
print fmt % (addr + i),
|
||||
ascii = ''
|
||||
for j in xrange(16):
|
||||
if i + j < len(data):
|
||||
print '%02x' % data[i + j],
|
||||
if 0x20 <= data[i+j] <= 0x7E:
|
||||
ascii += chr(data[i+j])
|
||||
else:
|
||||
ascii += '.'
|
||||
else:
|
||||
print ' ',
|
||||
ascii += ' '
|
||||
if j == 7:
|
||||
print '',
|
||||
ascii += ' '
|
||||
print '|', ascii
|
||||
|
||||
def reprompt(self):
|
||||
if self.started:
|
||||
self.prompt = 'ctu %s> ' % raw(self.mu.reg_read(UC_ARM64_REG_PC))
|
||||
else:
|
||||
self.prompt = 'ctu> '
|
||||
|
||||
def debug(self, sub=False):
|
||||
self.debugging = True
|
||||
self.reprompt()
|
||||
try:
|
||||
self.sublevel += 1
|
||||
while True:
|
||||
try:
|
||||
self.cmdloop()
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
print
|
||||
finally:
|
||||
self.sublevel -= 1
|
||||
if self.sublevel == 1:
|
||||
self.prompt = 'ctu> '
|
||||
|
||||
def debugbreak(self):
|
||||
try:
|
||||
self.debug(sub=True)
|
||||
except Restart:
|
||||
self.restarting = True
|
||||
return self.stop()
|
||||
|
||||
def print_topics(self, header, cmds, cmdlen, maxcol):
|
||||
nix = 'EOF', 'b', 'c', 's', 'r', 't'
|
||||
if header is not None:
|
||||
Cmd.print_topics(self, header, [cmd for cmd in cmds if cmd not in nix], cmdlen, maxcol)
|
||||
|
||||
def do_EOF(self, line):
|
||||
print
|
||||
try:
|
||||
if raw_input('Really exit? y/n: ').startswith('y'):
|
||||
sys.exit()
|
||||
except EOFError:
|
||||
print
|
||||
sys.exit()
|
||||
def do_exit(self, line):
|
||||
"""exit
|
||||
Exit the debugger."""
|
||||
sys.exit()
|
||||
|
||||
def do_start(self, line):
|
||||
"""s/start
|
||||
Start or restart the code."""
|
||||
if self.sublevel != 1:
|
||||
raise Restart()
|
||||
|
||||
while True:
|
||||
self.reset()
|
||||
try:
|
||||
self.execfunc(self)
|
||||
break
|
||||
except Restart:
|
||||
print 'got restart at', self.sublevel
|
||||
continue
|
||||
do_s = do_start
|
||||
|
||||
def do_trace(self, line):
|
||||
"""t/trace (i/instruction | b/block | f/function)
|
||||
Toggles tracing of instructions, blocks, or functions."""
|
||||
if line.startswith('i'):
|
||||
self.flags ^= TRACE_INSTRUCTION
|
||||
print 'Instruction tracing', 'on' if self.flags & TRACE_INSTRUCTION else 'off'
|
||||
elif line.startswith('b'):
|
||||
self.flags ^= TRACE_BLOCK
|
||||
print 'Block tracing', 'on' if self.flags & TRACE_BLOCK else 'off'
|
||||
elif line.startswith('f'):
|
||||
self.flags ^= TRACE_FUNCTION
|
||||
print 'Function tracing', 'on' if self.flags & TRACE_FUNCTION else 'off'
|
||||
else:
|
||||
print 'Unknown trace flag'
|
||||
do_t = do_trace
|
||||
|
||||
def do_break(self, addr):
|
||||
"""b/break [name]
|
||||
Without `name`, list breakpoints.
|
||||
Given a symbol name or address, toggle breakpoint."""
|
||||
if addr == '':
|
||||
print 'Breakpoints:'
|
||||
for addr in self.breakpoints:
|
||||
print '*', addr
|
||||
return
|
||||
|
||||
try:
|
||||
addr = raw(addr)
|
||||
except BadAddr:
|
||||
print 'Invalid address/symbol'
|
||||
return
|
||||
|
||||
if addr in self.breakpoints:
|
||||
print 'Removing breakpoint at %s' % addr
|
||||
self.breakpoints.remove(addr)
|
||||
else:
|
||||
print 'Breaking at %s' % addr
|
||||
self.breakpoints.add(addr)
|
||||
do_b = do_break
|
||||
def complete_break(self, text, line, begidx, endidx):
|
||||
ftext = line.split(' ', 1)[1] if ' ' in line else ''
|
||||
cut = len(ftext) - len(text)
|
||||
return [sym[cut:] for sym in symbols.keys() if sym.startswith(ftext)]
|
||||
complete_b = complete_break
|
||||
|
||||
def do_sym(self, name):
|
||||
"""sym <name>
|
||||
Prints the address of a given symbol."""
|
||||
try:
|
||||
print raw(name)
|
||||
except BadAddr:
|
||||
print 'Invalid address/symbol'
|
||||
complete_sym = complete_break
|
||||
|
||||
def do_continue(self, line):
|
||||
"""c/continue
|
||||
Continues execution of the code."""
|
||||
if self.sublevel == 1:
|
||||
print 'Not running'
|
||||
else:
|
||||
return True
|
||||
do_c = do_continue
|
||||
|
||||
def do_next(self, line):
|
||||
"""n/next
|
||||
Step to the next instruction."""
|
||||
if self.sublevel == 1:
|
||||
print 'Not running'
|
||||
else:
|
||||
self.singlestep = True
|
||||
return True
|
||||
do_n = do_next
|
||||
|
||||
def do_regs(self, line):
|
||||
"""r/reg/regs [reg [value]]
|
||||
No parameters: Display registers.
|
||||
Reg parameter: Display one register.
|
||||
Otherwise: Assign a value (always hex, or a symbol) to a register."""
|
||||
if line == '':
|
||||
return self.dumpregs()
|
||||
elif ' ' in line:
|
||||
r, v = line.split(' ', 1)
|
||||
try:
|
||||
v = raw(v)
|
||||
if self.reg(r, v) is None:
|
||||
print 'Invalid register'
|
||||
except BadAddr:
|
||||
print 'Invalid address/Symbol'
|
||||
else:
|
||||
v = self.reg(line)
|
||||
if v is False:
|
||||
print 'Invalid register'
|
||||
else:
|
||||
print '0x%016x' % v
|
||||
do_r = do_reg = do_regs
|
||||
|
||||
def do_exec(self, line):
|
||||
"""x/exec <code>
|
||||
Evaluates a given line of C."""
|
||||
try:
|
||||
val = ceval(line, self)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
print 'Execution failed'
|
||||
return
|
||||
|
||||
if val is not None:
|
||||
print '0x%x' % val
|
||||
do_x = do_exec
|
||||
|
||||
def do_dump(self, line):
|
||||
"""dump <address> [size]
|
||||
Dumps `size` (default: 0x100) bytes of memory at an address.
|
||||
If the address takes the form `*register` (e.g. `*X1`) then the value of that register will be used."""
|
||||
line = list(line.split(' '))
|
||||
if len(line[0]) == 0:
|
||||
print 'No address'
|
||||
elif len(line) <= 2:
|
||||
if len(line[0]) and line[0][0] == '*':
|
||||
line[0] = self.reg(line[0][1:])
|
||||
if line[0] is None:
|
||||
print 'Invalid register'
|
||||
return
|
||||
else:
|
||||
try:
|
||||
line[0] = raw(line[0])
|
||||
except BadAddr:
|
||||
print 'Invalid address/symbol'
|
||||
return
|
||||
if len(line) == 2:
|
||||
line[1] = parseInt(line[1])
|
||||
if line[1] is None or line[1] >= 0x10000:
|
||||
print 'Invalid size'
|
||||
return
|
||||
self.dumpmem(line[0], 0x100 if len(line) == 1 else line[1])
|
||||
else:
|
||||
print 'Too many parameters'
|
||||
|
||||
def do_ad(self, line):
|
||||
"""ad
|
||||
Toggle address display specialization."""
|
||||
Address.display_specialized = not Address.display_specialized
|
||||
print '%s specialized address display' % ('Enabled' if Address.display_specialized else 'Disabled')
|
||||
self.reprompt()
|
||||
|
||||
def do_watch(self, line):
|
||||
"""w/watch <expression>
|
||||
Breaks when expression evaluates to true.
|
||||
Without an expression, list existing watchpoints."""
|
||||
if line == '':
|
||||
print 'Watchpoints:'
|
||||
for code, _ in self.watchpoints:
|
||||
print '*', code
|
||||
return
|
||||
|
||||
if line in [code for code, _ in self.watchpoints]:
|
||||
self.watchpoints = [(code, func) for code, func in self.watchpoints if code != line]
|
||||
print 'Watchpoint deleted'
|
||||
else:
|
||||
self.watchpoints.append((line, compile(line)))
|
||||
print 'Watchpoint added'
|
||||
do_w = do_watch
|
||||
|
||||
def debug(func):
|
||||
ctu = CTU()
|
||||
ctu.setup(func)
|
||||
ctu.debug()
|
||||
return func
|
||||
|
||||
def run(flags=TRACE_NONE):
|
||||
def sub(func):
|
||||
ctu = CTU()
|
||||
ctu.setup(func)
|
||||
ctu.run(flags)
|
||||
return func
|
||||
|
||||
if callable(flags):
|
||||
func = flags
|
||||
flags = TRACE_NONE
|
||||
return sub(func)
|
||||
else:
|
||||
return sub
|
28
main.py
Normal file
28
main.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
from ctu import *
|
||||
from util import *
|
||||
from ceval import ceval
|
||||
|
||||
@run
|
||||
@debug
|
||||
def main(ctu):
|
||||
fPath = ctu.malloc(36)
|
||||
ctu.writemem(fPath, 'blacklist:/blacklist.txt\0')
|
||||
fOption = ctu.malloc(16)
|
||||
ctu.writemem(fOption, 'rb\0')
|
||||
print '%016x' % ctu.call(MainAddress(0x43ddb4), fPath, fOption)
|
||||
|
||||
#ctu.dumpmem(WKCAddress(0x887828), 0x1000)
|
||||
#ctu.call(WKCAddress(0x397B3C))
|
||||
|
||||
"""sbuf = ctu.malloc(32)
|
||||
ctu.writemem(sbuf + 4, struct.pack('<I', 0xAABCDDEF))
|
||||
obuf = ctu.malloc(0x1000)
|
||||
|
||||
print 'obuf at %x' % obuf
|
||||
ctu.call(MainAddress(0x397c68), sbuf, obuf, 0xDEADBEEF01234567, 0xCAFEBABE)"""
|
||||
|
||||
"""dname = ctu.malloc(64)
|
||||
ctu.writemem(dname, '/dev/nvmap')
|
||||
print '%016x' % ctu.call(MainAddress(0x1a49c4), dname)"""
|
||||
#print '%016x' % ctu.call(MainAddress(0x1a4b10), 0xdeadbeef, 0xcafebabe, 0x0123456789, 0xf0e0d0c0)
|
||||
#print '%016x' % ctu.call(MainAddress(0x1a4ae8), 0x6b0001)
|
4
requirements.txt
Normal file
4
requirements.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
unicorn>=1.0.0
|
||||
capstone>=3.0.0
|
||||
colorama>=0.3.7
|
||||
pycparser>=2.17
|
47
svc.py
Normal file
47
svc.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
from util import *
|
||||
import struct
|
||||
|
||||
handlers = {}
|
||||
def handler(num):
|
||||
def sub(func):
|
||||
handlers[num] = func
|
||||
return func
|
||||
return sub
|
||||
|
||||
class SvcHandler(object):
|
||||
def __init__(self, ctu):
|
||||
self.ctu = ctu
|
||||
|
||||
for i in xrange(0x60):
|
||||
ctu.hookinsn(0xD4000001 | (i << 5), (lambda i: lambda _, __: self.svcDispatch(i))(i))
|
||||
|
||||
def svcDispatch(self, svc):
|
||||
if svc in handlers:
|
||||
print 'svc %x' % svc
|
||||
handlers[svc](self)
|
||||
return False
|
||||
|
||||
print 'Unhandled: SVC 0x%02x @ %s' % (svc, raw(self.ctu.pc))
|
||||
self.ctu.debugbreak()
|
||||
return False
|
||||
|
||||
def ipcDispatcher(self, handle, addr, size):
|
||||
print 'IPC! Handle: %08x' % handle
|
||||
self.ctu.dumpmem(addr, size)
|
||||
|
||||
@handler(0x1D)
|
||||
def SignalEvent(self):
|
||||
self.ctu.reg(0, 0)
|
||||
|
||||
@handler(0x21)
|
||||
def SendSyncRequest(self):
|
||||
return self.ipcDispatcher(self.ctu.reg(0), self.ctu.tlsbase, 0x100)
|
||||
|
||||
@handler(0x22)
|
||||
def SendSyncRequestEx(self):
|
||||
return self.ipcDispatcher(self.ctu.reg(2), self.ctu.reg(0), self.ctu.reg(1))
|
||||
|
||||
@handler(0x25)
|
||||
def GetThreadId(self):
|
||||
self.ctu.writemem(self.ctu.reg(0), struct.pack('<Q', 0xf00))
|
||||
self.ctu.reg(0, 0)
|
109
util.py
Normal file
109
util.py
Normal file
|
@ -0,0 +1,109 @@
|
|||
import re
|
||||
|
||||
class Restart(Exception):
|
||||
pass
|
||||
class BadAddr(Exception):
|
||||
pass
|
||||
|
||||
symbols = {}
|
||||
invsyms = {}
|
||||
def mapLoader(fn, acls, base):
|
||||
cut = 'nullsub_', 'def_%s' % ('%x' % base)[:2]
|
||||
with file(fn, 'r') as fp:
|
||||
for line in fp:
|
||||
if not line.startswith(' 00000001:'):
|
||||
continue
|
||||
addr = acls(int(line[10:26], 16) - base)
|
||||
name = line[33:].strip().split('(', 1)[0]
|
||||
if any(name.startswith(x) for x in cut):
|
||||
continue
|
||||
symbols[name] = addr
|
||||
invsyms[addr] = name
|
||||
|
||||
class Address(object):
|
||||
display_specialized = True
|
||||
|
||||
def __init__(self, addr, pad=False, raw=False):
|
||||
self.addr = addr + (0 if raw else self.baseaddr)
|
||||
self.pad = pad
|
||||
|
||||
def __hash__(self):
|
||||
return self.addr.__hash__()
|
||||
|
||||
def __eq__(self, b):
|
||||
b = raw(b)
|
||||
return self.addr == b.addr
|
||||
|
||||
@property
|
||||
def baseaddr(self):
|
||||
return type(self).realbase
|
||||
|
||||
@property
|
||||
def offset(self):
|
||||
return self.addr - self.baseaddr
|
||||
|
||||
def to(self, cls):
|
||||
if isinstance(self, cls):
|
||||
return self
|
||||
return cls(self.addr, pad=self.pad, raw=True)
|
||||
|
||||
@property
|
||||
def symbol(self):
|
||||
if self in invsyms:
|
||||
return invsyms[self]
|
||||
|
||||
def __str__(self):
|
||||
if not Address.display_specialized and not isinstance(self, RawAddress):
|
||||
return self.to(RawAddress).__str__()
|
||||
sym = self.symbol
|
||||
if sym is None:
|
||||
return self.mname % (('0x%016x' if self.pad else '0x%x') % self.offset)
|
||||
else:
|
||||
return self.mname % (('%s @ 0x%016x' if self.pad else '%s @ 0x%x') % (sym, self.offset))
|
||||
|
||||
def __add__(self, b):
|
||||
return type(self)(self.addr + b, pad=self.pad, raw=True)
|
||||
|
||||
def specialize(self):
|
||||
if not isinstance(self, RawAddress):
|
||||
return self.to(RawAddress).specialize()
|
||||
|
||||
if WKCAddress.realbase <= self.addr < WKCAddress.realbase + WKCAddress.realsize:
|
||||
return self.to(WKCAddress)
|
||||
elif MainAddress.realbase <= self.addr < MainAddress.realbase + MainAddress.realbase:
|
||||
return self.to(MainAddress)
|
||||
return self
|
||||
|
||||
class RawAddress(Address):
|
||||
mname = '%s'
|
||||
realbase = 0
|
||||
realsize = 1 << 64
|
||||
class WKCAddress(Address):
|
||||
mname = 'WKC(%s)'
|
||||
# realbase/size assigned at runtime
|
||||
class MainAddress(Address):
|
||||
mname = 'Main(%s)'
|
||||
# realbase/size assigned at runtime
|
||||
|
||||
def raw(addr, pad=False):
|
||||
if isinstance(addr, str) or isinstance(addr, unicode):
|
||||
if addr in symbols:
|
||||
return symbols[addr]
|
||||
else:
|
||||
raise BadAddr()
|
||||
elif isinstance(addr, Address):
|
||||
return addr.to(RawAddress)
|
||||
return RawAddress(addr, pad=pad).specialize()
|
||||
def native(addr):
|
||||
if isinstance(addr, Address):
|
||||
return addr.addr
|
||||
else:
|
||||
return addr
|
||||
|
||||
def parseInt(addr, implicitHex=False):
|
||||
if (implicitHex or addr.startswith('0x')) and re.match(r'^(0x)?[0-9a-fA-F]+$', addr):
|
||||
return int(addr[2:] if addr.startswith('0x') else addr, 16)
|
||||
elif re.match(r'^[0-9]+$', addr):
|
||||
return int(addr)
|
||||
else:
|
||||
return None
|
19300
webkit_wkc.map
Normal file
19300
webkit_wkc.map
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue