2211 lines
83 KiB
Python
2211 lines
83 KiB
Python
import cgcrandom
|
|
import weakref
|
|
import sys, os, struct
|
|
from utils import qemu
|
|
from core.cpu.abstractcpu import Interruption, Syscall, ConcretizeRegister, InvalidPCException
|
|
from core.cpu.cpufactory import CpuFactory
|
|
from core.memory import SMemory32, SMemory64, Memory32, Memory64
|
|
from core.smtlib import Expression, Operators, ConstraintSet
|
|
from binary.elf.elftools.elf.elffile import ELFFile
|
|
from contextlib import closing
|
|
import StringIO
|
|
import logging
|
|
import random
|
|
from core.cpu.arm import *
|
|
from core.executor import SyscallNotImplemented, ProcessExit
|
|
logger = logging.getLogger("LINUX")
|
|
|
|
class RestartSyscall(Exception):
|
|
pass
|
|
|
|
class Deadlock(Exception):
|
|
pass
|
|
|
|
def perms_from_elf(elf_flags):
|
|
return [' ', ' x', ' w ', ' wx', 'r ', 'r x', 'rw ', 'rwx'][elf_flags&7]
|
|
|
|
def perms_from_protflags(prot_flags):
|
|
return [' ', 'r ', ' w ', 'rw ', ' x', 'r x', ' wx', 'rwx'][prot_flags&7]
|
|
|
|
class SymbolicSyscallArgument(Exception):
|
|
def __init__(self, reg_num, message='Concretizing syscall argument', policy='SAMPLED'):
|
|
self.reg_num = reg_num
|
|
self.message = message
|
|
self.policy = policy
|
|
super(SymbolicSyscallArgument, self).__init__(message)
|
|
|
|
|
|
class File(object):
|
|
def __init__(self, *args, **kwargs):
|
|
#Todo: assert file is seekable otherwise we should save wwhat was
|
|
#read/write to the state
|
|
self.file = file(*args,**kwargs)
|
|
def stat(self):
|
|
return os.fstat(self.fileno())
|
|
def ioctl(self, request, argp):
|
|
#argp ignored..
|
|
return fcntl.fcntl(self, request)
|
|
@property
|
|
def name(self):
|
|
return self.file.name
|
|
@property
|
|
def mode(self):
|
|
return self.file.mode
|
|
def tell(self, *args):
|
|
return self.file.tell(*args)
|
|
def seek(self, *args):
|
|
return self.file.seek(*args)
|
|
def write(self, *args):
|
|
return self.file.write(*args)
|
|
def read(self, *args):
|
|
return self.file.read(*args)
|
|
def close(self, *args):
|
|
return self.file.close(*args)
|
|
def fileno(self, *args):
|
|
return self.file.fileno(*args)
|
|
|
|
def __getstate__(self):
|
|
state = {}
|
|
state['name'] = self.name
|
|
state['mode'] = self.mode
|
|
state['pos'] = self.tell()
|
|
return state
|
|
|
|
def __setstate__(self, state):
|
|
name = state['name']
|
|
mode = state['mode']
|
|
pos = state['pos']
|
|
self.file = file(name, mode)
|
|
self.seek(pos)
|
|
|
|
class SymbolicFile(object):
|
|
'''
|
|
Represents a symbolic file
|
|
'''
|
|
def __init__(self, constraints, path="sfile", mode='rw', max_size=100, wildcard='+'):
|
|
'''
|
|
Builds a symbolic file
|
|
@param constraints: the constraints
|
|
@param path: the pathname of the symbolic file
|
|
@param mode: the access permissions of the symbolic file
|
|
@param max_size: Maximun amount of bytes of the symbolic file
|
|
'''
|
|
assert 'r' in mode
|
|
if isinstance(path, str):
|
|
path = File(path, mode)
|
|
assert isinstance(path, File)
|
|
|
|
#self._constraints = weakref.ref(constraints)
|
|
WILDCARD = '+'
|
|
|
|
symbols_cnt = 0
|
|
data = path.read()
|
|
size = len(data)
|
|
self.array = constraints.new_array(name=path.name, max_size=size)
|
|
for i in range(size):
|
|
if data[i] != WILDCARD:
|
|
self.array[i] = data[i]
|
|
else:
|
|
symbols_cnt+=1
|
|
|
|
self.pos = 0
|
|
self.max_size=min(len(data), max_size)
|
|
if symbols_cnt > max_size:
|
|
logger.warning("Found more wilcards in the file than free symbolic values allowed (%d > %d)",symbols_cnt, max_size)
|
|
else:
|
|
logger.debug("Found %d free symbolic values on file %s",symbols_cnt, path.name)
|
|
|
|
def __getstate__(self):
|
|
state = {}
|
|
state['array'] = self.array
|
|
state['pos'] = self.pos
|
|
state['max_size'] = self.max_size
|
|
return state
|
|
|
|
def __setstate__(self, state):
|
|
self.pos = state['pos']
|
|
self.max_size = state['max_size']
|
|
self.array = state['array']
|
|
|
|
@property
|
|
def constraints(self):
|
|
return self._constraints()
|
|
|
|
@property
|
|
def name(self):
|
|
return self.array.name
|
|
|
|
def ioctl(self, request, argp):
|
|
#logger.debug("IOCTL on symbolic files not implemented! (req:%x)", request)
|
|
return 0
|
|
|
|
def stat(self):
|
|
from collections import namedtuple
|
|
stat_result = namedtuple('stat_result', ['st_mode','st_ino','st_dev','st_nlink','st_uid','st_gid','st_size','st_atime','st_mtime','st_ctime', 'st_blksize','st_blocks','st_rdev'])
|
|
return stat_result(8592,11,9,1,1000,5,0,1378673920,1378673920,1378653796,0x400,0x8808,0)
|
|
|
|
def fileno(self):
|
|
'''
|
|
Not implemented
|
|
'''
|
|
pass
|
|
#return self.f.fileno()
|
|
|
|
def tell(self):
|
|
'''
|
|
Returns the read/write file offset
|
|
@rtype: int
|
|
@return: the read/write file offset.
|
|
'''
|
|
return self.pos
|
|
def seek(self, pos):
|
|
'''
|
|
Returns the read/write file offset
|
|
@rtype: int
|
|
@return: the read/write file offset.
|
|
'''
|
|
assert isinstance(pos, (int,long))
|
|
self.pos = pos
|
|
|
|
def read(self, count):
|
|
'''
|
|
Reads up to C{count} bytes from the file.
|
|
@rtype: list
|
|
@return: the list of symbolic bytes read
|
|
'''
|
|
if self.pos > self.max_size :
|
|
return []
|
|
else:
|
|
size = min(count,self.max_size-self.pos)
|
|
ret = [self.array[i] for i in xrange(self.pos,self.pos+size)]
|
|
self.pos+=size
|
|
return ret
|
|
|
|
def write(self, data):
|
|
'''
|
|
Writes the symbolic bytes in C{data} onto the file.
|
|
'''
|
|
for c in data:
|
|
size = min(len(data),self.max_size-self.pos)
|
|
for i in xrange(self.pos,self.pos+size):
|
|
self.array[i] = data[i-self.pos]
|
|
|
|
|
|
class Socket(object):
|
|
def stat(self):
|
|
from collections import namedtuple
|
|
stat_result = namedtuple('stat_result', ['st_mode','st_ino','st_dev','st_nlink','st_uid','st_gid','st_size','st_atime','st_mtime','st_ctime', 'st_blksize','st_blocks','st_rdev'])
|
|
return stat_result(8592,11,9,1,1000,5,0,1378673920,1378673920,1378653796,0x400,0x8808,0)
|
|
|
|
@staticmethod
|
|
def pair():
|
|
a = Socket()
|
|
b = Socket()
|
|
a.connect(b)
|
|
return a,b
|
|
|
|
def __init__(self):
|
|
self.buffer = [] #queue os bytes
|
|
self.peer = None
|
|
|
|
def __repr__(self):
|
|
return "SOCKET(%x, %r, %x)"%(hash(self), self.buffer, hash(self.peer))
|
|
|
|
def is_connected(self):
|
|
return self.peer is not None
|
|
|
|
def is_empty(self):
|
|
return len(self.buffer)==0
|
|
|
|
def is_full(self):
|
|
return len(self.buffer)>2*1024
|
|
|
|
def connect(self, peer):
|
|
assert not self.is_connected()
|
|
assert not peer.is_connected()
|
|
self.peer = peer
|
|
if peer.peer is None:
|
|
peer.peer = self
|
|
|
|
def read(self, size):
|
|
return self.receive(size)
|
|
|
|
def receive(self, size):
|
|
rx_bytes = min(size, len(self.buffer))
|
|
ret = []
|
|
for i in xrange(rx_bytes):
|
|
ret.append(self.buffer.pop())
|
|
return ret
|
|
|
|
def write(self, buf):
|
|
return self.transmit(buf)
|
|
|
|
def transmit(self, buf):
|
|
assert self.is_connected()
|
|
return self.peer._transmit(buf)
|
|
|
|
def _transmit(self, buf):
|
|
for c in buf:
|
|
self.buffer.insert(0,c)
|
|
return len(buf)
|
|
|
|
|
|
class Linux(object):
|
|
'''
|
|
A simple Linux Operating System Model.
|
|
This class emulates the most common Linux system calls
|
|
'''
|
|
CGC_EBADF=1
|
|
CGC_EFAULT=2
|
|
CGC_EINVAL=3
|
|
CGC_ENOMEM=4
|
|
CGC_ENOSYS=5
|
|
CGC_EPIPE=6
|
|
CGC_SSIZE_MAX=2147483647
|
|
CGC_SIZE_MAX=4294967295
|
|
CGC_FD_SETSIZE=32
|
|
|
|
ARM_GET_TLS=0xffff0fe0
|
|
ARM_CMPXCHG=0xffff0fc0
|
|
ARM_MEM_BARRIER=0xffff0fa0
|
|
|
|
def __init__(self, program, argv=[], envp=[]):
|
|
'''
|
|
Builds a Linux OS model
|
|
@param cpus: CPU for this model.
|
|
@param mem: memory for this model.
|
|
@todo: generalize for more CPUs.
|
|
@todo: fix deps?
|
|
'''
|
|
self.program = program
|
|
self.clocks = 0
|
|
self.files = []
|
|
self.syscall_trace = []
|
|
|
|
self.files = []
|
|
# open standard files stdin, stdout, stderr
|
|
logger.debug("Opening file descriptors (0,1,2)")
|
|
self.input = Socket()
|
|
self.output = Socket()
|
|
self.stderr = Socket()
|
|
|
|
stdin = Socket()
|
|
stdout = Socket()
|
|
stderr = Socket()
|
|
#A transmit to stdin,stdout or stderr will be directed to out
|
|
stdin.peer = self.output
|
|
stdout.peer = self.output
|
|
stderr.peer = self.stderr
|
|
#A receive from stdin will get data from inp
|
|
self.input.peer = stdin
|
|
#A receive on stdout or stderr will return no data (rx_bytes: 0)
|
|
|
|
assert self._open(stdin) == 0
|
|
assert self._open(stdout) == 1
|
|
assert self._open(stderr) == 2
|
|
|
|
#Load process and setup socketpairs
|
|
self.procs = []
|
|
arch = {'x86': 'i386', 'x64': 'amd64', 'ARM': 'armv7'}[ELFFile(file(program)).get_machine_arch()]
|
|
cpu = self._mk_proc(arch)
|
|
self.load(cpu, program)
|
|
self._arch_reg_init(cpu, arch)
|
|
self.setup_stack(cpu, [program]+argv, envp)
|
|
|
|
|
|
self.procs.append(cpu)
|
|
|
|
nprocs = len(self.procs)
|
|
nfiles = len(self.files)
|
|
assert nprocs > 0
|
|
self.running = range(nprocs)
|
|
self._current = 0
|
|
|
|
#Each process can wait for one timeout
|
|
self.timers = [ None ] * nprocs
|
|
#each fd has a waitlist
|
|
self.rwait = [set() for _ in xrange(nfiles)]
|
|
self.twait = [set() for _ in xrange(nfiles)]
|
|
|
|
def _mk_proc(self, arch):
|
|
if arch in {'i386', 'armv7'}:
|
|
mem = Memory32()
|
|
else:
|
|
mem = Memory64()
|
|
return CpuFactory.get_cpu(mem, arch)
|
|
|
|
@property
|
|
def current(self):
|
|
return self.procs[self._current]
|
|
|
|
def __getstate__(self):
|
|
state = {}
|
|
state['clocks'] = self.clocks
|
|
state['input'] = self.input.buffer
|
|
state['output'] = self.output.buffer
|
|
|
|
files = []
|
|
for fd in self.files:
|
|
if isinstance(fd, Socket):
|
|
files.append(('Socket', fd.buffer))
|
|
else:
|
|
files.append(('File',fd))
|
|
state['files'] = files
|
|
|
|
state['procs'] = self.procs
|
|
state['current'] = self._current
|
|
state['running'] = self.running
|
|
state['rwait'] = self.rwait
|
|
state['twait'] = self.twait
|
|
state['timers'] = self.timers
|
|
state['syscall_trace'] = self.syscall_trace
|
|
state['base'] = self.base
|
|
state['elf_bss'] = self.elf_bss
|
|
state['end_code'] = self.end_code
|
|
state['end_data'] = self.end_data
|
|
state['elf_brk'] = self.elf_brk
|
|
state['auxv'] = self.auxv
|
|
state['program'] = self.program
|
|
if hasattr(self, 'tls_value'):
|
|
state['tls_value'] = self.tls_value
|
|
return state
|
|
|
|
def __setstate__(self, state):
|
|
"""
|
|
@todo: some asserts
|
|
@todo: fix deps? (last line)
|
|
"""
|
|
self.input = Socket()
|
|
self.input.buffer = state['input']
|
|
self.output = Socket()
|
|
self.output.buffer = state['output']
|
|
|
|
self.files = []
|
|
for ty, buf in state['files']:
|
|
if ty == 'Socket':
|
|
f = Socket()
|
|
f.buffer = buf
|
|
self.files.append(f)
|
|
else:
|
|
self.files.append(buf)
|
|
#for fd in range(len(self.files)):
|
|
# if self.connections(fd) is not None:
|
|
# self.files[fd].peer = self.files[self.connections(fd)]
|
|
|
|
self.files[0].peer = self.output
|
|
self.files[1].peer = self.output
|
|
self.files[2].peer = self.output
|
|
self.input.peer = self.files[0]
|
|
|
|
self.procs = state['procs']
|
|
self._current = state['current']
|
|
self.running = state['running']
|
|
self.rwait = state['rwait']
|
|
self.twait = state['twait']
|
|
self.timers = state['timers']
|
|
self.clocks = state['clocks']
|
|
|
|
self.syscall_trace = state['syscall_trace']
|
|
self.base = state['base']
|
|
self.elf_bss = state['elf_bss']
|
|
self.end_code = state['end_code']
|
|
self.end_data = state['end_data']
|
|
self.elf_brk = state['elf_brk']
|
|
self.auxv = state['auxv']
|
|
self.program = state['program']
|
|
if 'tls_value' in state:
|
|
self.tls_value = state['tls_value']
|
|
|
|
def _read_string(self, cpu, buf):
|
|
"""
|
|
Reads a null terminated concrete buffer form memory
|
|
@todo: FIX. move to cpu or memory
|
|
"""
|
|
filename = ""
|
|
for i in xrange(0,1024):
|
|
c = Operators.CHR(cpu.read_int(buf + i, 8))
|
|
if c == '\x00':
|
|
break
|
|
filename += c
|
|
return filename
|
|
|
|
def load_vdso(self, bits):
|
|
#load vdso #TODO or #IGNORE
|
|
vdso_top = {32: 0x7fff0000, 64: 0x7fff00007fff0000}[bits]
|
|
vdso_size = len(file('vdso%2d.dump'%bits).read())
|
|
vdso_addr = cpu.memory.mmapFile(cpu.memory._floor(vdso_top - vdso_size),
|
|
vdso_size, 'r x',
|
|
{32: 'vdso32.dump', 64: 'vdso64.dump'}[bits],
|
|
0 )
|
|
return vdso_addr
|
|
|
|
|
|
def setup_stack(self, cpu, argv, envp):
|
|
'''
|
|
@param argv: list of parameters for the program to execute.
|
|
@param envp: list of environment variables for the program to execute.
|
|
|
|
http://www.phrack.org/issues.html?issue=58&id=5#article
|
|
position content size (bytes) + comment
|
|
----------------------------------------------------------------------
|
|
stack pointer -> [ argc = number of args ] 4
|
|
[ argv[0] (pointer) ] 4 (program name)
|
|
[ argv[1] (pointer) ] 4
|
|
[ argv[..] (pointer) ] 4 * x
|
|
[ argv[n - 1] (pointer) ] 4
|
|
[ argv[n] (pointer) ] 4 (= NULL)
|
|
|
|
[ envp[0] (pointer) ] 4
|
|
[ envp[1] (pointer) ] 4
|
|
[ envp[..] (pointer) ] 4
|
|
[ envp[term] (pointer) ] 4 (= NULL)
|
|
|
|
[ auxv[0] (Elf32_auxv_t) ] 8
|
|
[ auxv[1] (Elf32_auxv_t) ] 8
|
|
[ auxv[..] (Elf32_auxv_t) ] 8
|
|
[ auxv[term] (Elf32_auxv_t) ] 8 (= AT_NULL vector)
|
|
|
|
[ padding ] 0 - 16
|
|
|
|
[ argument ASCIIZ strings ] >= 0
|
|
[ environment ASCIIZ str. ] >= 0
|
|
|
|
(0xbffffffc) [ end marker ] 4 (= NULL)
|
|
|
|
(0xc0000000) < top of stack > 0 (virtual)
|
|
----------------------------------------------------------------------
|
|
'''
|
|
# TODO cpu.STACK_push_bytes() pls
|
|
def push_bytes(data):
|
|
cpu.STACK -= len(data)
|
|
cpu.write_bytes(cpu.STACK, data)
|
|
return cpu.STACK
|
|
|
|
def push_int(value):
|
|
cpu.STACK -= cpu.address_bit_size/8
|
|
cpu.write_int(cpu.STACK, value, cpu.address_bit_size)
|
|
return cpu.STACK
|
|
|
|
|
|
auxv = self.auxv
|
|
logger.debug("Setting argv, envp and auxv.")
|
|
logger.debug("\tArguments: %s"%repr(argv))
|
|
logger.debug("\tEnvironment:")
|
|
for e in envp:
|
|
logger.debug("\t\t%s"%repr(e))
|
|
|
|
|
|
#We save the argument and environment pointers
|
|
argvlst=[]
|
|
envplst=[]
|
|
|
|
#end envp marker empty string
|
|
for evar in envp:
|
|
push_bytes('\x00')
|
|
envplst.append(push_bytes(evar))
|
|
|
|
for arg in argv:
|
|
push_bytes('\x00')
|
|
argvlst.append(push_bytes(arg))
|
|
|
|
|
|
#Put all auxv strings into the string stack area.
|
|
#And replace the value be its pointer
|
|
|
|
for name, value in auxv.items():
|
|
if hasattr(value, '__len__'):
|
|
push_bytes(value)
|
|
auxv[name]=cpu.STACK
|
|
|
|
#The "secure execution" mode of secure_getenv() is controlled by the
|
|
#AT_SECURE flag contained in the auxiliary vector passed from the
|
|
#kernel to user space.
|
|
auxvnames = {'AT_IGNORE': 1, # Entry should be ignored
|
|
'AT_EXECFD': 2, # File descriptor of program
|
|
'AT_PHDR': 3, # Program headers for program
|
|
'AT_PHENT':4, # Size of program header entry
|
|
'AT_PHNUM':5, # Number of program headers
|
|
'AT_PAGESZ': 6, # System page size
|
|
'AT_BASE': 7, # Base address of interpreter
|
|
'AT_FLAGS':8, # Flags
|
|
'AT_ENTRY':9, # Entry point of program
|
|
'AT_NOTELF': 10, # Program is not ELF
|
|
'AT_UID':11, # Real uid
|
|
'AT_EUID': 12, # Effective uid
|
|
'AT_GID':13, # Real gid
|
|
'AT_EGID': 14, # Effective gid
|
|
'AT_CLKTCK': 17, # Frequency of times()
|
|
'AT_PLATFORM': 15, # String identifying platform.
|
|
'AT_HWCAP':16, # Machine-dependent hints about processor capabilities.
|
|
'AT_FPUCW':18, # Used FPU control word.
|
|
'AT_SECURE': 23, # Boolean, was exec setuid-like?
|
|
'AT_BASE_PLATFORM': 24, # String identifying real platforms.
|
|
'AT_RANDOM': 25, # Address of 16 random bytes.
|
|
'AT_EXECFN': 31, # Filename of executable.
|
|
'AT_SYSINFO':32, #Pointer to the global system page used for system calls and other nice things.
|
|
'AT_SYSINFO_EHDR': 33, #Pointer to the global system page used for system calls and other nice things.
|
|
}
|
|
#AT_NULL
|
|
push_int(0)
|
|
push_int(0)
|
|
for name, val in auxv.items():
|
|
push_int(val)
|
|
push_int(auxvnames[name])
|
|
|
|
|
|
# NULL ENVP
|
|
push_int(0)
|
|
for var in reversed(envplst): # ENVP n
|
|
push_int(var)
|
|
envp = cpu.STACK
|
|
|
|
# NULL ARGV
|
|
push_int(0)
|
|
for arg in reversed(argvlst): # Argv n
|
|
push_int(arg)
|
|
argv = cpu.STACK
|
|
|
|
#ARGC
|
|
push_int(len(argvlst))
|
|
|
|
|
|
def load(self, cpu, filename):
|
|
'''
|
|
Loads and an ELF program in memory and prepares the initial CPU state.
|
|
Creates the stack and loads the environment variables and the arguments in it.
|
|
@param filename: pathname of the file to be executed.
|
|
@raise error:
|
|
- 'Not matching cpu': if the program is compiled for a different architecture
|
|
- 'Not matching memory': if the program is compiled for a different address size
|
|
@todo: define va_randomize and read_implies_exec personality
|
|
'''
|
|
#load elf See binfmt_elf.c
|
|
#read the ELF object file
|
|
elf = ELFFile(file(filename))
|
|
arch = {'x86':'i386','x64':'amd64', 'ARM': 'armv7'}[elf.get_machine_arch()]
|
|
addressbitsize = {'x86':32, 'x64':64, 'ARM': 32}[elf.get_machine_arch()]
|
|
logger.debug("Loading %s as a %s elf"%(filename,arch))
|
|
|
|
assert elf.header.e_type in ['ET_DYN', 'ET_EXEC', 'ET_CORE']
|
|
|
|
#Get interpreter elf
|
|
interpreter = None
|
|
for elf_segment in elf.iter_segments():
|
|
if elf_segment.header.p_type != 'PT_INTERP':
|
|
continue
|
|
interpreter = ELFFile(file(elf_segment.data()[:-1]))
|
|
break
|
|
if not interpreter is None:
|
|
assert interpreter.get_machine_arch() == elf.get_machine_arch()
|
|
assert interpreter.header.e_type in ['ET_DYN', 'ET_EXEC']
|
|
|
|
#Stack Executability
|
|
executable_stack = False
|
|
for elf_segment in elf.iter_segments():
|
|
if elf_segment.header.p_type != 'PT_GNU_STACK':
|
|
continue
|
|
if elf_segment.header.p_flags & 0x01:
|
|
executable_stack = True
|
|
else:
|
|
executable_stack = False
|
|
break
|
|
|
|
base = 0
|
|
elf_bss = 0
|
|
end_code = 0
|
|
end_data = 0
|
|
elf_brk = 0
|
|
load_addr = 0
|
|
|
|
for elf_segment in elf.iter_segments():
|
|
if elf_segment.header.p_type != 'PT_LOAD':
|
|
continue
|
|
|
|
align = 0x1000 #elf_segment.header.p_align
|
|
|
|
ELF_PAGEOFFSET = elf_segment.header.p_vaddr & (align-1)
|
|
|
|
flags = elf_segment.header.p_flags
|
|
memsz = elf_segment.header.p_memsz + ELF_PAGEOFFSET
|
|
offset = elf_segment.header.p_offset - ELF_PAGEOFFSET
|
|
filesz = elf_segment.header.p_filesz + ELF_PAGEOFFSET
|
|
vaddr = elf_segment.header.p_vaddr - ELF_PAGEOFFSET
|
|
memsz = cpu.memory._ceil(memsz+1) #FIXME Should this be +1 / (memsz + align ) & ~(align-1)
|
|
if base == 0 and elf.header.e_type == 'ET_DYN':
|
|
assert vaddr == 0
|
|
if addressbitsize == 32:
|
|
base = 0x56555000
|
|
else:
|
|
base = 0x555555554000
|
|
|
|
perms = perms_from_elf(flags)
|
|
hint = base+vaddr
|
|
if hint == 0:
|
|
hint = None
|
|
|
|
logger.debug("Loading elf offset: %08x addr:%08x %08x %s" %(offset, base+vaddr, base+vaddr+memsz, perms))
|
|
base = cpu.memory.mmapFile(hint,memsz,perms,elf_segment.stream.name,offset) - vaddr
|
|
|
|
if load_addr == 0 :
|
|
load_addr = base + vaddr
|
|
|
|
k = base + vaddr + filesz;
|
|
if k > elf_bss :
|
|
elf_bss = k;
|
|
if (flags & 4) and end_code < k: #PF_X
|
|
end_code = k
|
|
if end_data < k:
|
|
end_data = k
|
|
k = base + vaddr + memsz
|
|
if k > elf_brk:
|
|
elf_brk = k
|
|
|
|
elf_entry = elf.header.e_entry
|
|
if elf.header.e_type == 'ET_DYN':
|
|
elf_entry += load_addr
|
|
entry = elf_entry
|
|
real_elf_brk = elf_brk
|
|
|
|
# We need to explicitly zero any fractional pages
|
|
# after the data section (i.e. bss). This would
|
|
# contain the junk from the file that should not
|
|
# be in memory
|
|
#TODO:
|
|
#cpu.write_bytes(elf_bss, '\x00'*((elf_bss | (align-1))-elf_bss))
|
|
|
|
logger.debug("Zeroing main elf fractional pages. From %x to %x.", elf_bss, elf_brk)
|
|
logger.debug("Main elf bss:%x"%elf_bss)
|
|
logger.debug("Main elf brk %x:"%elf_brk)
|
|
|
|
#FIXME Need a way to inspect maps and perms so
|
|
#we can roollback all to the initial state after zeroing
|
|
#if elf_brk-elf_bss > 0:
|
|
# saved_perms = cpu.mem.perms(elf_bss)
|
|
# cpu.memory.mprotect(cpu.mem._ceil(elf_bss), elf_brk-elf_bss, 'rw ')
|
|
# logger.debug("Zeroing main elf fractional pages (%d bytes)", elf_brk-elf_bss)
|
|
# cpu.write_bytes(elf_bss, ['\x00'] * (elf_brk-elf_bss))
|
|
# cpu.memory.mprotect(cpu.memory._ceil(elf_bss), elf_brk-elf_bss, saved_perms)
|
|
|
|
|
|
if cpu.memory.access_ok(slice(elf_bss,elf_brk), 'w'):
|
|
cpu.memory[elf_bss:elf_brk] = '\x00'*(elf_brk-elf_bss)
|
|
else:
|
|
logger.warning("Failing to zerify the trailing: elf_brk-elf_bss")
|
|
|
|
stack_size = 0x21000
|
|
|
|
if addressbitsize == 32:
|
|
stack_top = 0xc0000000
|
|
else:
|
|
stack_top = 0x800000000000
|
|
stack_base = stack_top - stack_size
|
|
stack = cpu.memory.mmap(stack_base, stack_size, 'rwx', name='stack') + stack_size
|
|
assert stack_top == stack
|
|
|
|
reserved = cpu.memory.mmap(base+vaddr+memsz,0x1000000,' ')
|
|
interpreter_base = 0
|
|
if not interpreter is None:
|
|
base = 0
|
|
elf_bss = 0
|
|
end_code = 0
|
|
end_data = 0
|
|
elf_brk = 0
|
|
entry = interpreter.header.e_entry
|
|
for elf_segment in interpreter.iter_segments():
|
|
if elf_segment.header.p_type != 'PT_LOAD':
|
|
continue
|
|
align = 0x1000#elf_segment.header.p_align
|
|
vaddr = elf_segment.header.p_vaddr
|
|
filesz = elf_segment.header.p_filesz
|
|
flags = elf_segment.header.p_flags
|
|
offset = elf_segment.header.p_offset
|
|
memsz = elf_segment.header.p_memsz
|
|
|
|
ELF_PAGEOFFSET = (vaddr & (align-1))
|
|
memsz = memsz + ELF_PAGEOFFSET
|
|
offset = offset - ELF_PAGEOFFSET
|
|
filesz = filesz + ELF_PAGEOFFSET
|
|
vaddr = vaddr - ELF_PAGEOFFSET
|
|
memsz = cpu.memory._ceil(memsz)
|
|
|
|
if base == 0 and elf.header.e_type == 'ET_DYN':
|
|
assert vaddr == 0
|
|
base = stack_base - memsz
|
|
|
|
if base == 0:
|
|
assert vaddr == 0
|
|
perms = perms_from_elf(flags)
|
|
hint = base+vaddr
|
|
if hint == 0:
|
|
hint = None
|
|
base = cpu.memory.mmapFile(hint, memsz, perms, elf_segment.stream.name, offset) - vaddr
|
|
|
|
logger.debug("Loading interpreter offset: %08x addr:%08x %08x %s%s%s" %(offset, base+vaddr, base+vaddr+memsz, (flags&1 and 'r' or ' '), (flags&2 and 'w' or ' '), (flags&4 and 'x' or ' ')))
|
|
|
|
k = base + vaddr+ filesz;
|
|
if k > elf_bss :
|
|
elf_bss = k;
|
|
if (flags & 4) and end_code < k: #PF_X
|
|
end_code = k
|
|
if end_data < k:
|
|
end_data = k
|
|
k = base + vaddr+ memsz
|
|
if k > elf_brk:
|
|
elf_brk = k
|
|
|
|
if interpreter.header.e_type == 'ET_DYN':
|
|
entry += base
|
|
interpreter_base = base
|
|
|
|
logger.debug("Zeroing interpreter elf fractional pages. From %x to %x.", elf_bss, elf_brk)
|
|
logger.debug("Interpreter bss:%x"%elf_bss)
|
|
logger.debug("Interpreter brk %x:"%elf_brk)
|
|
|
|
cpu.memory.mprotect(cpu.memory._floor(elf_bss), elf_brk-elf_bss, 'rw ')
|
|
try:
|
|
cpu.memory[elf_bss:elf_brk] = '\x00'*(elf_brk-elf_bss)
|
|
except Exception, e:
|
|
logger.debug("Exception zeroing Interpreter fractional pages: %s"%str(e))
|
|
#TODO #FIXME mprotect as it was before zeroing?
|
|
|
|
|
|
#free reserved brk space
|
|
cpu.memory.munmap(reserved, 0x1000000)
|
|
|
|
#load vdso
|
|
#vdso_addr = load_vdso(addressbitsize)
|
|
|
|
cpu.STACK = stack
|
|
cpu.PC = entry
|
|
|
|
logger.debug("Entry point: %016x", entry)
|
|
logger.debug("Stack start: %016x", stack)
|
|
logger.debug("Brk: %016x", real_elf_brk)
|
|
logger.debug("Mappings:")
|
|
for m in str(cpu.memory).split('\n'):
|
|
logger.debug(" %s", m)
|
|
self.base = base
|
|
self.elf_bss = elf_bss
|
|
self.end_code = end_code
|
|
self.end_data = end_data
|
|
self.elf_brk = real_elf_brk
|
|
|
|
|
|
#put auxv strings in stack
|
|
# TODO move into cpu as cpu.stack_push(), possibly removing the need for stack_sub, stack_add?
|
|
def push_bytes( value ):
|
|
cpu.STACK -= len(value)
|
|
cpu.write_bytes(cpu.STACK, value)
|
|
return cpu.STACK
|
|
|
|
at_platform = push_bytes('x86_%02d\x00'%addressbitsize)
|
|
at_random = push_bytes('A'*16)
|
|
at_execfn = push_bytes(filename+'\x00')
|
|
|
|
|
|
auxv = {}
|
|
auxv['AT_PHDR'] = load_addr+elf.header.e_phoff # Program headers for program
|
|
auxv['AT_PHENT'] = elf.header.e_ehsize # Size of program header entry
|
|
auxv['AT_PHNUM'] = elf.header.e_phnum # Number of program headers
|
|
auxv['AT_PAGESZ'] = cpu.memory.page_size # System page size
|
|
auxv['AT_BASE'] = interpreter_base # Base address of interpreter
|
|
auxv['AT_FLAGS'] = elf.header.e_flags # Flags
|
|
auxv['AT_ENTRY'] = elf_entry # Entry point of program
|
|
auxv['AT_UID'] = 1000 # Real uid
|
|
auxv['AT_EUID'] = 1000 # Effective uid
|
|
auxv['AT_GID'] = 1000 # Real gid
|
|
auxv['AT_EGID'] = 1000 # Effective gid
|
|
auxv['AT_CLKTCK'] = 100 # Frequency of times()
|
|
auxv['AT_PLATFORM'] = at_platform # String identifying platform.
|
|
auxv['AT_HWCAP'] = 0xbfebfbff # Machine-dependent hints about processor capabilities.
|
|
auxv['AT_RANDOM'] = at_random # Address of 16 random bytes.
|
|
auxv['AT_EXECFN'] = at_execfn # Filename of executable.
|
|
self.auxv = auxv
|
|
|
|
def _open(self, f):
|
|
'''
|
|
It opens a file on the given a file descriptor
|
|
@rtype: int
|
|
@param filename: pathname of the file to open.
|
|
@param mode: file permissions mode.
|
|
@return: a file description of the opened file.
|
|
'''
|
|
if None in self.files:
|
|
fd = self.files.index(None)
|
|
self.files[fd]=f
|
|
else:
|
|
fd = len(self.files)
|
|
self.files.append(f)
|
|
return fd
|
|
|
|
def _close(self, fd):
|
|
'''
|
|
Closes a file descriptor
|
|
@rtype: int
|
|
@param fd: the file descriptor to close.
|
|
@return: C{0} on success.
|
|
'''
|
|
self.files[fd] = None
|
|
|
|
def _dup(self, fd):
|
|
'''
|
|
Duplicates a file descriptor
|
|
@rtype: int
|
|
@param fd: the file descriptor to close.
|
|
@return: C{0} on success.
|
|
'''
|
|
return self._open(self.files[fd])
|
|
|
|
def _is_open(self, fd):
|
|
return fd >= 0 and fd < len(self.files) and self.files[fd] is not None
|
|
|
|
def sys_allocate(self, cpu, length, isX, addr):
|
|
''' allocate - allocate virtual memory
|
|
|
|
The allocate system call creates a new allocation in the virtual address
|
|
space of the calling process. The length argument specifies the length of
|
|
the allocation in bytes which will be rounded up to the hardware page size.
|
|
|
|
The kernel chooses the address at which to create the allocation; the
|
|
address of the new allocation is returned in *addr as the result of the call.
|
|
|
|
All newly allocated memory is readable and writeable. In addition, the
|
|
is_X argument is a boolean that allows newly allocated memory to be marked
|
|
as executable (non-zero) or non-executable (zero).
|
|
|
|
The allocate function is invoked through system call number 5.
|
|
|
|
@param cpu current CPU
|
|
@parm length the length of the allocation in bytes
|
|
@parm isX boolean that allows newly allocated memory to be marked
|
|
as executable
|
|
@parm addr the address of the new allocation is returned in *addr
|
|
|
|
@return On success, allocate returns zero and a pointer to the allocated area
|
|
is returned in *addr. Otherwise, an error code is returned
|
|
and *addr is undefined.
|
|
EINVAL length is zero.
|
|
EINVAL length is too large.
|
|
EFAULT addr points to an invalid address.
|
|
ENOMEM No memory is available or the process' maximum number of allocations
|
|
would have been exceeded.
|
|
'''
|
|
#TODO: check 4 bytes from addr
|
|
if not cpu.memory.isValid(addr):
|
|
logger.info("ALLOCATE: addr points to invalid address. Rerurning EFAULT")
|
|
return Linux.CGC_EFAULT
|
|
|
|
perms = [ 'rw ', 'rwx'][bool(isX)]
|
|
try:
|
|
result = cpu.memory.mmap(None, length, perms)
|
|
except Exception,e:
|
|
logger.info("ALLOCATE exception %s. Returning ENOMEM", str(e))
|
|
return Linux.CGC_ENOMEM
|
|
cpu.write_int(addr, result, cpu.address_bit_size)
|
|
logger.debug("ALLOCATE(%d, %s, 0x%08x) -> 0x%08x"%(length, perms, addr, result))
|
|
|
|
return 0
|
|
|
|
def sys_lseek(self, cpu, fd, offset, whence):
|
|
''' lseek - reposition read/write file offset
|
|
|
|
The lseek() function repositions the file offset of the open file description associated
|
|
with the file descriptor fd to the argument offset according to the directive whence
|
|
|
|
|
|
@param self current CPU.
|
|
@param fd a valid file descripor
|
|
@param offset the offset in bytes
|
|
@param whence SEEK_SET: The file offset is set to offset bytes.
|
|
SEEK_CUR: The file offset is set to its current location plus offset bytes.
|
|
SEEK_END: The file offset is set to the size of the file plus offset bytes.
|
|
|
|
@result 0 Success
|
|
EBADF fd is not a valid file descriptor or is not open
|
|
|
|
'''
|
|
if not self._is_open(fd):
|
|
logger.info("LSEEK: Not valid file descriptor on lseek. Returning EBADF")
|
|
return Linux.CGC_EBADF
|
|
|
|
if isinstance(self.files[fd], Socket):
|
|
logger.info("LSEEK: Not valid file descriptor on lseek. Fd not seekable. Returning EBADF")
|
|
return Linux.CGC_EBADF
|
|
|
|
# Read the data and put in tin memory
|
|
self.files[fd].seek(offset)
|
|
#self.syscall_trace.append(("_seek", fd, offset, whence))
|
|
|
|
logger.debug("LSEEK(%d, 0x%08x, %d)"%(fd, offset, whence))
|
|
return 0
|
|
|
|
def sys_read(self, cpu, fd, buf, count):
|
|
''' receive - receive bytes from a file descriptor
|
|
|
|
The receive system call reads up to count bytes from file descriptor fd to the
|
|
buffer pointed to by buf. If count is zero, receive returns 0 and optionally
|
|
dets *rx_bytes to zero.
|
|
|
|
@param self current CPU.
|
|
@param fd a valid file descripor
|
|
@param buf a memory buffer
|
|
@param count max number of bytes to receive
|
|
@param rx_bytes if valid, points to the actual number of bytes received
|
|
@result 0 Success
|
|
EBADF fd is not a valid file descriptor or is not open
|
|
EFAULT buf or rx_bytes points to an invalid address.
|
|
'''
|
|
data = ''
|
|
if count != 0:
|
|
if not self._is_open(fd):
|
|
logger.info("RECEIVE: Not valid file descriptor on receive. Returning EBADF")
|
|
return Linux.CGC_EBADF
|
|
|
|
# TODO check count bytes from buf
|
|
if not buf in cpu.memory: # or not cpu.memory.isValid(buf+count):
|
|
logger.info("RECEIVE: buf points to invalid address. Returning EFAULT")
|
|
return Linux.CGC_EFAULT
|
|
|
|
if isinstance(self.files[fd],Socket) and self.files[fd].is_empty():
|
|
return 0
|
|
|
|
# Read the data and put in tin memory
|
|
data = self.files[fd].read(count)
|
|
self.syscall_trace.append(("_receive", fd, data))
|
|
cpu.write_bytes(buf, data)
|
|
|
|
logger.debug("RECEIVE(%d, 0x%08x, %d, 0x%08x) -> <%s> (size:%d)"%(fd, buf, count, len(data), repr(data)[:min(count,10)],len(data)))
|
|
return len(data)
|
|
|
|
def sys_write(self, cpu, fd, buf, count):
|
|
''' transmit - send bytes through a file descriptor
|
|
The transmit system call writes up to count bytes from the buffer pointed
|
|
to by buf to the file descriptor fd. If count is zero, transmit returns 0
|
|
and optionally sets *tx_bytes to zero.
|
|
|
|
@param cpu current CPU
|
|
@param fd a valid file descripor
|
|
@param buf a memory buffer
|
|
@param count number of bytes to send
|
|
@result 0 Success
|
|
EBADF fd is not a valid file descriptor or is not open.
|
|
EFAULT buf or tx_bytes points to an invalid address.
|
|
'''
|
|
data = []
|
|
if count != 0:
|
|
|
|
if not self._is_open(fd):
|
|
logger.error("TRANSMIT: Not valid file descriptor. Returning EBADFD %d", fd)
|
|
return Linux.CGC_EBADF
|
|
|
|
# TODO check count bytes from buf
|
|
if buf not in cpu.memory or buf+count not in cpu.memory:
|
|
logger.debug("TRANSMIT: buf points to invalid address. Rerurning EFAULT")
|
|
return Linux.CGC_EFAULT
|
|
|
|
if fd > 2 and self.files[fd].is_full():
|
|
cpu.PC -= cpu.instruction.size
|
|
self.wait([],[fd],None)
|
|
raise RestartSyscall()
|
|
|
|
data = cpu.read_bytes(buf, count)
|
|
self.files[fd].transmit(data)
|
|
|
|
for line in ''.join([str(x) for x in data]).split('\n'):
|
|
logger.debug("TRANSMIT(%d, 0x%08x, %d) -> <%.48r>"%(fd, buf, count, line))
|
|
self.syscall_trace.append(("_transmit", fd, data))
|
|
self.signal_transmit(fd)
|
|
|
|
return len(data)
|
|
|
|
def sys_access(self, cpu, buf, mode):
|
|
'''
|
|
Checks real user's permissions for a file
|
|
@rtype: int
|
|
|
|
@param cpu: current CPU.
|
|
@param buf: a buffer containing the pathname to the file to check its permissions.
|
|
@param mode: the access permissions to check.
|
|
@return:
|
|
- C{0} if the calling process can access the file in the desired mode.
|
|
- C{-1} if the calling process can not access the file in the desired mode.
|
|
'''
|
|
filename = ""
|
|
for i in xrange(0,255):
|
|
c = Operators.CHR(cpu.read_int(buf + i, 8))
|
|
if c == '\x00':
|
|
break
|
|
filename += c
|
|
|
|
#if path.isfile(PATH) and access(PATH, MODE):
|
|
# print "File exists and is readable"
|
|
#else:
|
|
# print "Either file is missing or is not readable"
|
|
logger.debug("access(%s, %x) -> %r", filename, mode, os.access(filename, mode))
|
|
if os.access(filename, mode):
|
|
return 0
|
|
else:
|
|
return -1
|
|
|
|
def sys_uname(self, cpu, old_utsname):
|
|
'''
|
|
Writes system information in the variable C{old_utsname}.
|
|
@rtype: int
|
|
@param cpu: current CPU.
|
|
@param old_utsname: the buffer to write the system info.
|
|
@return: C{0} on success
|
|
'''
|
|
from datetime import datetime
|
|
def pad(s):
|
|
return s +'\x00'*(65-len(s))
|
|
uname = pad('Linux')
|
|
uname += pad('localhost')
|
|
uname += pad('8.1.6-gentoo')
|
|
uname += pad('#4 SMP '+ datetime.now().strftime("%a %b %d %H:%M:%S ART %Y") )
|
|
uname += pad('x86_64')
|
|
uname += pad('(none)')
|
|
cpu.write_bytes(old_utsname, uname)
|
|
logger.debug("sys_uname(...) -> %s", uname)
|
|
return 0
|
|
|
|
def sys_brk(self, cpu, brk):
|
|
'''
|
|
Changes data segment size (moves the C{elf_brk} to the new address)
|
|
@rtype: int
|
|
@param cpu: current CPU.
|
|
@param brk: the new address for C{elf_brk}.
|
|
@return: the value of the new C{elf_brk}.
|
|
@raise error:
|
|
- "Error in brk!" if there is any error allocating the memory
|
|
'''
|
|
if brk != 0:
|
|
assert brk > self.elf_brk
|
|
size = brk-self.elf_brk
|
|
perms = cpu.memory.perms(self.elf_brk-1)
|
|
if brk > cpu.memory._ceil(self.elf_brk-1):
|
|
addr = cpu.memory.mmap(cpu.memory._ceil(self.elf_brk-1), size, perms)
|
|
assert cpu.memory._ceil(self.elf_brk-1) == addr, "Error in brk!"
|
|
self.elf_brk += size
|
|
logger.debug("sys_brk(0x%08x) -> 0x%08x", brk, self.elf_brk)
|
|
return self.elf_brk
|
|
|
|
def sys_arch_prctl(self, cpu, code, addr):
|
|
'''
|
|
Sets architecture-specific thread state
|
|
@rtype: int
|
|
|
|
@param cpu: current CPU.
|
|
@param code: must be C{ARCH_SET_FS}.
|
|
@param addr: the base address of the FS segment.
|
|
@return: C{0} on success
|
|
@raise error:
|
|
- if C{code} is different to C{ARCH_SET_FS}
|
|
'''
|
|
ARCH_SET_GS = 0x1001
|
|
ARCH_SET_FS = 0x1002
|
|
ARCH_GET_FS = 0x1003
|
|
ARCH_GET_GS = 0x1004
|
|
assert code == ARCH_SET_FS
|
|
cpu.FS=0x63
|
|
cpu.set_descriptor(cpu.FS, addr, 0x4000, 'rw')
|
|
logger.debug("sys_arch_prctl(%04x, %016x) -> 0", code, addr)
|
|
return 0
|
|
|
|
def sys_ioctl(self, cpu, fd, request, argp):
|
|
if fd > 2:
|
|
return self.files[fd].ioctl(request, argp)
|
|
else:
|
|
return 0
|
|
|
|
|
|
def sys_open(self, cpu, buf, flags, mode):
|
|
# buf: address of zero-terminated pathname
|
|
# flags/access: file access bits
|
|
# perms: file permission mode
|
|
filename = self._read_string(cpu, buf)
|
|
try :
|
|
if os.path.abspath(filename).startswith('/proc/self'):
|
|
if filename == '/proc/self/exe':
|
|
filename = self.program
|
|
else:
|
|
logger.info("FIXME!")
|
|
pass
|
|
mode = { os.O_RDWR: 'r+', os.O_RDONLY: 'r', os.O_WRONLY: 'w' }[flags&7]
|
|
f = File(filename, mode) #todo modes, flags
|
|
logger.debug("Openning file %s for %s real fd %d",filename, mode, f.fileno())
|
|
except Exception,e:
|
|
logger.info("Could not open file %s. Reason %s"%(filename,str(e)))
|
|
return -1
|
|
if filename in self.symbolic_files:
|
|
logger.debug("%s file is considered to have symbols."%filename)
|
|
assert flags&7 == os.O_RDWR or flags&7 == os.O_RDONLY, "Symbolic files should be readable?"
|
|
f = SymbolicFile(self.constraints, f, 'r')
|
|
|
|
return self._open(f)
|
|
|
|
def sys_getpid(self, cpu, v):
|
|
logger.debug("GETPID, warning pid modeled as concrete 1000")
|
|
return 1000
|
|
|
|
def sys_ARM_NR_set_tls(self, cpu, val):
|
|
self.tls_value = val
|
|
return 0
|
|
|
|
#Signals..
|
|
def sys_kill(self, cpu, pid, sig):
|
|
logger.debug("KILL, Ignoring Sending signal %d to pid %d", sig, pid )
|
|
return 0
|
|
|
|
def sys_sigaction(self, cpu, signum, act, oldact):
|
|
logger.debug("SIGACTION, Ignoring chaging signal handler for signal %d", signum)
|
|
return 0
|
|
|
|
def sys_sigprocmask(self, how, newset, oldset):
|
|
logger.debug("SIGACTION, Ignoring chaging signal mask set cmd:%d", how)
|
|
return 0
|
|
|
|
def sys_close(self, cpu, fd):
|
|
'''
|
|
Closes a file descriptor
|
|
@rtype: int
|
|
@param cpu: current CPU.
|
|
@param fd: the file descriptor to close.
|
|
@return: C{0} on success.
|
|
'''
|
|
if fd > 0 :
|
|
self._close(fd)
|
|
logger.debug('sys_close(%d)', fd)
|
|
return 0
|
|
|
|
def sys_readlink(self, cpu, path, buf, bufsize):
|
|
'''
|
|
Read
|
|
@rtype: int
|
|
|
|
@param cpu: current CPU.
|
|
@param path: the "link path id"
|
|
@param buf: the buffer where the bytes will be putted.
|
|
@param bufsize: the max size for read the link.
|
|
@todo: Out eax number of bytes actually sent | EAGAIN | EBADF | EFAULT | EINTR | EINVAL | EIO | ENOSPC | EPIPE
|
|
'''
|
|
if bufsize <= 0:
|
|
return -EINVAL
|
|
filename = self._read_string(cpu, path)
|
|
data = os.readlink(filename)[:bufsize]
|
|
cpu.write_bytes(buf, data)
|
|
logger.debug("READLINK %d %x %d -> %s",path,buf,bufsize,data)
|
|
return len(data)
|
|
|
|
def sys_mprotect(self, cpu, start, size, prot):
|
|
'''
|
|
Sets protection on a region of memory. Changes protection for the calling process's
|
|
memory page(s) containing any part of the address range in the interval [C{start}, C{start}+C{size}-1].
|
|
@rtype: int
|
|
|
|
@param cpu: current CPU.
|
|
@param start: the starting address to change the permissions.
|
|
@param size: the size of the portion of memory to change the permissions.
|
|
@param prot: the new acces premission for the memory.
|
|
@return: C{0} on success.
|
|
'''
|
|
perms = perms_from_protflags(prot)
|
|
ret = cpu.memory.mprotect(start, size, perms)
|
|
logger.debug("sys_mprotect(0x%016x, 0x%x, %s) -> %r (%r)", start, size, perms, ret, prot)
|
|
return 0
|
|
|
|
def sys_munmap(self, cpu, addr, size):
|
|
'''
|
|
Unmaps a file from memory. It deletes the mappings for the specified address range
|
|
@rtype: int
|
|
|
|
@param cpu: current CPU.
|
|
@param addr: the starting address to unmap.
|
|
@param size: the size of the portion to unmap.
|
|
@return: C{0} on success.
|
|
'''
|
|
cpu.memory.munmap(addr, size)
|
|
return 0
|
|
|
|
def sys_getuid(self, cpu):
|
|
'''
|
|
Gets user identity.
|
|
@rtype: int
|
|
|
|
@param cpu: current CPU.
|
|
@return: this call returns C{1000} for all the users.
|
|
'''
|
|
return 1000
|
|
def sys_getgid(self, cpu):
|
|
'''
|
|
Gets group identity.
|
|
@rtype: int
|
|
|
|
@param cpu: current CPU.
|
|
@return: this call returns C{1000} for all the groups.
|
|
'''
|
|
return 1000
|
|
def sys_geteuid(self, cpu):
|
|
'''
|
|
Gets user identity.
|
|
@rtype: int
|
|
|
|
@param cpu: current CPU.
|
|
@return: This call returns C{1000} for all the users.
|
|
'''
|
|
return 1000
|
|
def sys_getegid(self, cpu):
|
|
'''
|
|
Gets group identity.
|
|
@rtype: int
|
|
|
|
@param cpu: current CPU.
|
|
@return: this call returns C{1000} for all the groups.
|
|
'''
|
|
return 1000
|
|
|
|
def sys_writev(self, cpu, fd, iov, count):
|
|
'''
|
|
Works just like C{sys_write} except that multiple buffers are written out (for Linux 64 bits).
|
|
@rtype: int
|
|
|
|
@param cpu: current CPU.
|
|
@param fd: the file descriptor of the file to write.
|
|
@param iov: the buffer where the the bytes to write are taken.
|
|
@param count: amount of C{iov} buffers to write into the file.
|
|
@return: the amount of bytes written in total.
|
|
'''
|
|
total = 0
|
|
for i in xrange(0, count):
|
|
buf = cpu.read_int(iov + i * 16, 64)
|
|
size = cpu.read_int(iov + i * 16 + 8, 64)
|
|
|
|
data = ""
|
|
for i in xrange(0,size):
|
|
data += Operators.CHR(cpu.read_int(buf + i, 8))
|
|
logger.debug("WRITEV(%r, %r, %r) -> <%r> (size:%r)"%(fd, buf, size, data, len(data)))
|
|
self.files[fd].write(data)
|
|
total+=size
|
|
return total
|
|
|
|
def sys_writev32(self, cpu, fd, iov, count):
|
|
'''
|
|
Works just like C{sys_write} except that multiple buffers are written out. (32 bit version)
|
|
@rtype: int
|
|
|
|
@param cpu: current CPU.
|
|
@param fd: the file descriptor of the file to write.
|
|
@param iov: the buffer where the the bytes to write are taken.
|
|
@param count: amount of C{iov} buffers to write into the file.
|
|
@return: the amount of bytes written in total.
|
|
'''
|
|
total = 0
|
|
for i in xrange(0, count):
|
|
buf = cpu.read_int(iov + i * 8, 32)
|
|
size = cpu.read_int(iov + i * 8 + 4, 32)
|
|
|
|
data = ""
|
|
for i in xrange(0,size):
|
|
data += Operators.CHR(cpu.read_int(buf + i, 8))
|
|
self.files[fd].write(Operators.CHR(cpu.read_int(buf + i, 8)))
|
|
logger.debug("WRITEV(%r, %r, %r) -> <%r> (size:%r)"%(fd, buf, size, data, len(data)))
|
|
total+=size
|
|
return total
|
|
|
|
def sys_set_thread_area32(self, cpu, user_info):
|
|
'''
|
|
Sets a thread local storage (TLS) area. Sets the base address of the GS segment.
|
|
@rtype: int
|
|
|
|
@param cpu: current CPU.
|
|
@param user_info: the TLS array entry set corresponds to the value of C{u_info->entry_number}.
|
|
@return: C{0} on success.
|
|
'''
|
|
n = cpu.read_int(user_info, 32)
|
|
pointer = cpu.read_int(user_info + 4, 32)
|
|
m = cpu.read_int(user_info + 8, 32)
|
|
flags = cpu.read_int(user_info + 12, 32)
|
|
assert n == 0xffffffff
|
|
assert flags == 0x51 #TODO: fix
|
|
cpu.GS=0x63
|
|
cpu.set_descriptor(cpu.GS, pointer, 0x4000, 'rw')
|
|
cpu.write_int(user_info, (0x63 - 3) / 8, 32)
|
|
return 0
|
|
|
|
def sys_getpriority(self, cpu, which, who):
|
|
'''
|
|
System call ignored.
|
|
@rtype: int
|
|
|
|
@return: C{0}
|
|
'''
|
|
logger.debug("Ignoring sys_get_priority")
|
|
return 0
|
|
|
|
def sys_setpriority(self, cpu, which, who, prio):
|
|
'''
|
|
System call ignored.
|
|
@rtype: int
|
|
|
|
@return: C{0}
|
|
'''
|
|
logger.debug("Ignoring sys_set_priority")
|
|
return 0
|
|
|
|
def sys_acct(self, cpu, path):
|
|
'''
|
|
System call not implemented.
|
|
@rtype: int
|
|
|
|
@return: C{-1}
|
|
'''
|
|
logger.debug("BSD account not implemented!")
|
|
return -1
|
|
|
|
def sys_exit_group(self, cpu, error_code):
|
|
'''
|
|
Exits all threads in a process
|
|
@param cpu: current CPU.
|
|
@raise Exception: 'Finished'
|
|
'''
|
|
procid = self.procs.index(cpu)
|
|
self.sched()
|
|
self.running.remove(procid)
|
|
#self.procs[procid] = None # TODO(mark) ask felipe why this commented
|
|
logger.debug("TERMINATE PROC_%02d %s", procid, error_code)
|
|
if len(self.running) == 0 :
|
|
raise ProcessExit(error_code)
|
|
return error_code
|
|
|
|
def sys_deallocate(self, cpu, addr, size):
|
|
''' deallocate - remove allocations
|
|
The deallocate system call deletes the allocations for the specified
|
|
address range, and causes further references to the addresses within the
|
|
range to generate invalid memory accesses. The region is also
|
|
automatically deallocated when the process is terminated.
|
|
|
|
The address addr must be a multiple of the page size. The length parameter
|
|
specifies the size of the region to be deallocated in bytes. All pages
|
|
containing a part of the indicated range are deallocated, and subsequent
|
|
references will terminate the process. It is not an error if the indicated
|
|
range does not contain any allocated pages.
|
|
|
|
The deallocate function is invoked through system call number 6.
|
|
|
|
@param cpu: current CPU
|
|
@param addr: the starting address to unmap.
|
|
@param size: the size of the portion to unmap.
|
|
@return 0 On success
|
|
EINVAL addr is not page aligned.
|
|
EINVAL length is zero.
|
|
EINVAL any part of the region being deallocated is outside the valid
|
|
address range of the process.
|
|
|
|
@param cpu: current CPU.
|
|
@return: C{0} on success.
|
|
'''
|
|
logger.debug("DEALLOCATE(0x%08x, %d)"%(addr, size))
|
|
|
|
if addr & 0xfff != 0:
|
|
logger.info("DEALLOCATE: addr is not page aligned")
|
|
return Linux.CGC_EINVAL
|
|
if size == 0 :
|
|
logger.info("DEALLOCATE:length is zero")
|
|
return Linux.CGC_EINVAL
|
|
#unlikely AND WRONG!!!
|
|
#if addr > Decree.CGC_SSIZE_MAX or addr+size > Decree.CGC_SSIZE_MAX:
|
|
# logger.info("DEALLOCATE: part of the region being deallocated is outside the valid address range of the process")
|
|
# return Decree.CGC_EINVAL
|
|
|
|
cpu.memory.munmap(addr, size)
|
|
return 0
|
|
|
|
def sys_fdwait(self, cpu, nfds, readfds, writefds, timeout, readyfds):
|
|
''' fdwait - wait for file descriptors to become ready
|
|
'''
|
|
logger.debug("FDWAIT(%d, 0x%08x, 0x%08x, 0x%08x, 0x%08x)"%(nfds, readfds, writefds, timeout, readyfds))
|
|
|
|
if timeout:
|
|
if timeout not in cpu.memory: #todo: size
|
|
logger.info("FDWAIT: timeput is pointing to invalid memory. Returning EFAULT")
|
|
return Linux.CGC_EFAULT
|
|
|
|
if readyfds:
|
|
if readyfds not in cpu.memory:
|
|
logger.info("FDWAIT: readyfds pointing to invalid memory. Returning EFAULT")
|
|
return Linux.CGC_EFAULT
|
|
|
|
writefds_wait = set()
|
|
writefds_ready = set()
|
|
|
|
if writefds:
|
|
if writefds not in cpu.memory:
|
|
logger.info("FDWAIT: writefds pointing to invalid memory. Returning EFAULT")
|
|
return Linux.CGC_EFAULT
|
|
bits = cpu.read_int(writefds, (nfds + 7) / 8)
|
|
|
|
for fd in range(nfds):
|
|
if (bits & 1<<fd):
|
|
if self.files[fd].is_full():
|
|
writefds_wait.add(fd)
|
|
else:
|
|
writefds_ready.add(fd)
|
|
|
|
readfds_wait = set()
|
|
readfds_ready = set()
|
|
if readfds:
|
|
if readfds not in cpu.memory:
|
|
logger.info("FDWAIT: readfds pointing to invalid memory. Returning EFAULT")
|
|
return Linux.CGC_EFAULT
|
|
bits = cpu.read_int(readfds, (nfds + 7) / 8)
|
|
for fd in range(nfds):
|
|
if (bits & 1<<fd):
|
|
if self.files[fd].is_empty():
|
|
readfds_wait.add(fd)
|
|
else:
|
|
readfds_ready.add(fd)
|
|
|
|
n = len(readfds_ready) + len(writefds_ready)
|
|
if n == 0:
|
|
seconds = cpu.read_int(timeout, 32)
|
|
microseconds = cpu.read_int(timeout + 4, 32)
|
|
logger.debug("FDWAIT: waiting for read on fds: {%s} and write to: {%s} timeout: %d", repr(list(readfds_wait)), repr(list(writefds_wait)), microseconds+1000*seconds)
|
|
#no ready file, wait
|
|
cpu.PC -= cpu.instruction.size
|
|
self.wait(readfds_wait, writefds_wait, microseconds+1000*seconds)
|
|
raise RestartSyscall() #When comming back from a timeout remember
|
|
#not to backtrack instruction and set EAX to 0! :( uglyness alert!
|
|
|
|
if readfds:
|
|
bits = 0
|
|
for fd in readfds_ready:
|
|
bits |= 1<<fd
|
|
for byte in range(0, nfds,8):
|
|
cpu.write_int(readfds, (bits >> byte) & 0xff, 8)
|
|
if writefds:
|
|
bits = 0
|
|
for fd in writefds_ready:
|
|
bits |= 1<<fd
|
|
for byte in range(0, nfds,8):
|
|
cpu.write_int(writefds, (bits >> byte) & 0xff, 8)
|
|
|
|
logger.debug("FDWAIT: continuing. Some file is ready Readyfds: %08x", readyfds)
|
|
if readyfds:
|
|
cpu.write_int(readyfds, n, 32)
|
|
return 0
|
|
|
|
def sys_ptrace(self, cpu, request, pid, addr, data):
|
|
logger.debug("sys_ptrace(%016x, %d, %016x, %016x) -> 0", request, pid, addr, data)
|
|
return 0
|
|
def sys_nanosleep(self, cpu, req, rem):
|
|
logger.debug("sys_nanosleep(...)")
|
|
return 0
|
|
def sys_set_tid_address(self, cpu, tidptr):
|
|
logger.debug("sys_set_tid_address(%016x) -> 0", tidptr)
|
|
return 1000 #tha pid
|
|
def sys_faccessat(self, cpu, dirfd, pathname, mode, flags):
|
|
filename = self._read_string(cpu, pathname)
|
|
logger.debug("sys_faccessat(%016x, %s, %x, %x) -> 0", dirfd, filename, mode, flags)
|
|
return -1
|
|
|
|
def sys_set_robust_list(self, cpu, head, length):
|
|
logger.debug("sys_set_robust_list(%016x, %d) -> -1", head, length)
|
|
return -1
|
|
def sys_futex(self, cpu, uaddr, op, val, timeout, uaddr2, val3):
|
|
logger.debug("sys_futex(...) -> -1")
|
|
return -1
|
|
def sys_getrlimit(self, cpu, resource, rlim):
|
|
logger.debug("sys_getrlimit(%x, %x) -> -1",resource, rlim)
|
|
return -1
|
|
def sys_fadvise64(self, cpu, fd, offset, length, advice):
|
|
logger.debug("sys_fadvise64(%x, %x, %x, %x) -> 0", fd, offset, length, advice)
|
|
return 0
|
|
def sys_gettimeofday(self, cpu, tv, tz):
|
|
logger.debug("sys_gettimeofday(%x, %x) -> 0", tv, tz)
|
|
return 0
|
|
|
|
#Distpatchers...
|
|
def syscall(self, cpu):
|
|
'''
|
|
64 bit dispatcher.
|
|
@param cpu: current CPU.
|
|
'''
|
|
syscalls = {
|
|
0x0000000000000008: self.sys_lseek,
|
|
0x000000000000000c: self.sys_brk,
|
|
0x000000000000000e: self.sys_sigprocmask,
|
|
0x000000000000009e: self.sys_arch_prctl,
|
|
0x0000000000000002: self.sys_open,
|
|
0x0000000000000000: self.sys_read,
|
|
0x0000000000000003: self.sys_close,
|
|
0x0000000000000005: self.sys_fstat64,
|
|
0x0000000000000009: self.sys_mmap,
|
|
0x0000000000000001: self.sys_write,
|
|
0x0000000000000027: self.sys_getpid,
|
|
0x000000000000003e: self.sys_kill,
|
|
0x0000000000000065: self.sys_ptrace,
|
|
0x0000000000000066: self.sys_getuid,
|
|
0x0000000000000068: self.sys_getgid,
|
|
0x000000000000006b: self.sys_geteuid,
|
|
0x000000000000006c: self.sys_getegid,
|
|
0x00000000000000e7: self.sys_exit_group,
|
|
0x0000000000000015: self.sys_access,
|
|
0x000000000000000a: self.sys_mprotect,
|
|
0x000000000000000b: self.sys_munmap,
|
|
0x0000000000000014: self.sys_writev,
|
|
0x0000000000000004: self.sys_stat64,
|
|
0x0000000000000059: self.sys_acct,
|
|
0x0000000000000023: self.sys_nanosleep,
|
|
# 0x0000000000000029: self.sys_socket,
|
|
# 0x000000000000002a: self.sys_connect,
|
|
# 0x000000000000002b: self.sys_accept,
|
|
# 0x000000000000002c: self.sys_sendto,
|
|
# 0x000000000000002d: self.sys_recvfrom,
|
|
# 0x000000000000002e: self.sys_sendmsg,
|
|
# 0x000000000000002f: self.sys_recvmsg,
|
|
# 0x0000000000000030: self.sys_shutdown,
|
|
# 0x0000000000000031: self.sys_bind,
|
|
# 0x0000000000000032: self.sys_listen,
|
|
# 0x0000000000000033: self.sys_getsockname,
|
|
# 0x0000000000000034: self.sys_getpeername,
|
|
# 0x0000000000000035: self.sys_socketpair,
|
|
# 0x0000000000000036: self.sys_setsockopt,
|
|
# 0x0000000000000037: self.sys_getsockopt,
|
|
0x000000000000003f: self.sys_uname,
|
|
0x00000000000000c9: self.sys_time,
|
|
0x00000000000000da: self.sys_set_tid_address,
|
|
0x00000000000000da: self.sys_faccessat,
|
|
0x0000000000000111: self.sys_set_robust_list,
|
|
0x00000000000000ca: self.sys_futex,
|
|
0x000000000000000d: self.sys_sigaction,
|
|
0x0000000000000060: self.sys_gettimeofday,
|
|
0x0000000000000061: self.sys_getrlimit,
|
|
0x00000000000000dd: self.sys_fadvise64,
|
|
0x00000000000000e4: self.sys_clock_gettime,
|
|
|
|
|
|
}
|
|
index, arguments, writeResult = cpu.get_syscall_description()
|
|
|
|
if index not in syscalls:
|
|
raise SyscallNotImplemented(64, index)
|
|
func = syscalls[index]
|
|
|
|
logger.debug("SYSCALL64: %s %r ", func.func_name
|
|
, arguments[:func.func_code.co_argcount])
|
|
nargs = func.func_code.co_argcount
|
|
args = [ cpu ] + arguments
|
|
|
|
result = func(*args[:nargs-1])
|
|
writeResult(result)
|
|
return result
|
|
|
|
def int80(self, cpu):
|
|
'''
|
|
32 bit dispatcher.
|
|
@param cpu: current CPU.
|
|
'''
|
|
syscalls = { 0x00000001: self.sys_exit_group,
|
|
0x00000003: self.sys_read,
|
|
0x00000004: self.sys_write,
|
|
0x00000005: self.sys_open,
|
|
0x00000006: self.sys_close,
|
|
0x00000021: self.sys_access,
|
|
0x00000025: self.sys_kill,
|
|
0x0000002d: self.sys_brk,
|
|
0x00000036: self.sys_ioctl,
|
|
0x0000004e: self.sys_gettimeofday,
|
|
0x00000055: self.sys_readlink,
|
|
0x00000059: self.sys_acct,
|
|
0x0000005b: self.sys_munmap,
|
|
0x0000007a: self.sys_uname,
|
|
0x0000007d: self.sys_mprotect,
|
|
0x0000008c: self.sys_setpriority,
|
|
0x0000008d: self.sys_getpriority,
|
|
0x00000092: self.sys_writev32,
|
|
0x000000c0: self.sys_mmap2,
|
|
0x000000c5: self.sys_fstat,
|
|
0x000000c7: self.sys_getuid,
|
|
0x000000c8: self.sys_getgid,
|
|
0x000000c9: self.sys_geteuid,
|
|
0x000000ca: self.sys_getegid,
|
|
0x000000f3: self.sys_set_thread_area32,
|
|
0x000000fc: self.sys_exit_group,
|
|
0x000000ae: self.sys_sigaction,
|
|
0x000000f8: self.sys_exit_group, # XXX
|
|
# 0x00000066: self.sys_socketcall,
|
|
# 0x000000dd: self.sys_fcntl64,
|
|
0x0000000d: self.sys_time,
|
|
0x00000014: self.sys_getpid,
|
|
0x000f0005: self.sys_ARM_NR_set_tls,
|
|
}
|
|
index, arguments, writeResult = cpu.get_syscall_description()
|
|
|
|
if index not in syscalls:
|
|
raise SyscallNotImplemented(64, index)
|
|
func = syscalls[index]
|
|
|
|
logger.debug("int80: %s %r ", func.func_name
|
|
, arguments[:func.func_code.co_argcount])
|
|
nargs = func.func_code.co_argcount
|
|
args = [ cpu ] + arguments
|
|
|
|
result = func(*args[:nargs-1])
|
|
writeResult(result)
|
|
return result
|
|
|
|
def sys_clock_gettime(self, cpu, clock_id, timespec):
|
|
logger.info("sys_clock_time not really implemented")
|
|
return 0
|
|
|
|
def sys_time(self, cpu, tloc):
|
|
import time
|
|
t = time.time()
|
|
if tloc != 0 :
|
|
cpu.write_int(tloc, int(t), cpu.address_bit_size)
|
|
return int(t)
|
|
|
|
|
|
def sched(self):
|
|
''' Yield CPU.
|
|
This will choose another process from the RUNNNIG list and change
|
|
current running process. May give the same cpu if only one running
|
|
proccess.
|
|
'''
|
|
if len(self.procs)>1:
|
|
logger.debug("SCHED:")
|
|
logger.debug("\tProcess: %r", self.procs)
|
|
logger.debug("\tRunning: %r", self.running)
|
|
logger.debug("\tRWait: %r", self.rwait)
|
|
logger.debug("\tTWait: %r", self.twait)
|
|
logger.debug("\tTimers: %r", self.timers)
|
|
logger.debug("\tCurrent clock: %d", self.clocks)
|
|
logger.debug("\tCurrent cpu: %d", self._current)
|
|
|
|
if len(self.running) == 0:
|
|
logger.debug("None running checking if there is some process waiting for a timeout")
|
|
if all([x is None for x in self.timers]):
|
|
raise Deadlock()
|
|
self.clocks = min(filter(lambda x: x is not None, self.timers))+1
|
|
self.check_timers()
|
|
assert len(self.running) != 0, "DEADLOCK!"
|
|
self._current = self.running[0]
|
|
return
|
|
next_index = (self.running.index(self._current) + 1) % len(self.running)
|
|
next = self.running[ next_index ]
|
|
if len(self.procs)>1:
|
|
logger.debug("\tTransfer control from process %d to %d", self._current, next)
|
|
self._current = next
|
|
|
|
def wait(self, readfds, writefds, timeout):
|
|
''' Wait for filedescriptors or timout.
|
|
Adds the current proceess in the correspondant wainting list and
|
|
yield the cpu to another running process.
|
|
'''
|
|
logger.debug("WAIT:")
|
|
logger.debug("\tProcess %d is going to wait for [ %r %r %r ]", self._current, readfds, writefds, timeout)
|
|
logger.debug("\tProcess: %r", self.procs)
|
|
logger.debug("\tRunning: %r", self.running)
|
|
logger.debug("\tRWait: %r", self.rwait)
|
|
logger.debug("\tTWait: %r", self.twait)
|
|
logger.debug("\tTimers: %r", self.timers)
|
|
|
|
|
|
for fd in readfds:
|
|
self.rwait[fd].add(self._current)
|
|
for fd in writefds:
|
|
self.twait[fd].add(self._current)
|
|
if timeout is not None:
|
|
self.timers[self._current] = self.clocks + timeout
|
|
procid = self._current
|
|
#self.sched()
|
|
next_index = (self.running.index(procid) + 1) % len(self.running)
|
|
self._current = self.running[ next_index ]
|
|
logger.debug("\tTransfer control from process %d to %d", procid, self._current)
|
|
logger.debug( "\tREMOVING %r from %r. Current: %r",procid, self.running, self._current)
|
|
self.running.remove(procid)
|
|
if self._current not in self.running:
|
|
logger.debug( "\tCurrent not running. Checking for timers...")
|
|
self._current=None
|
|
self.check_timers()
|
|
|
|
def awake(self, procid):
|
|
''' Remove procid from waitlists and restablish it in the running list '''
|
|
logger.debug("Remove procid:%d from waitlists and restablish it in the running list", procid)
|
|
for wait_list in self.rwait:
|
|
if procid in wait_list: wait_list.remove(procid)
|
|
for wait_list in self.twait:
|
|
if procid in wait_list: wait_list.remove(procid)
|
|
self.timers[procid]=None
|
|
self.running.append(procid)
|
|
if self._current is None:
|
|
self._current = procid
|
|
|
|
def connections(self, fd):
|
|
if fd in [0,1,2]:
|
|
return None
|
|
if fd%2:
|
|
return fd + 1
|
|
else:
|
|
return fd - 1
|
|
|
|
def signal_receive(self, fd):
|
|
''' Awake one process waiting to receive data on fd '''
|
|
connections = self.connections
|
|
if connections(fd) and self.twait[connections(fd)]:
|
|
procid = random.sample(self.twait[connections(fd)], 1)[0]
|
|
self.awake(procid)
|
|
|
|
def signal_transmit(self, fd):
|
|
''' Awake one process waiting to transmit data on fd '''
|
|
connections = self.connections
|
|
if connections(fd) and self.rwait[connections(fd)]:
|
|
procid = random.sample(self.rwait[connections(fd)], 1)[0]
|
|
self.awake(procid)
|
|
|
|
def check_timers(self):
|
|
''' Awake proccess if timer has expired '''
|
|
if self._current is None :
|
|
#Advance the clocks. Go to future!!
|
|
advance = min([self.clocks] + filter(lambda x: x is not None, self.timers)) +1
|
|
logger.debug("Advancing the clock from %d to %d", self.clocks, advance)
|
|
self.clocks = advance
|
|
for procid in range(len(self.timers)):
|
|
if self.timers[procid] is not None:
|
|
if self.clocks > self.timers[procid]:
|
|
self.procs[procid].PC += self.procs[procid].instruction.size
|
|
self.awake(procid)
|
|
|
|
def handleInvalidPC(self, e):
|
|
cpu = self.current
|
|
if cpu.PC == self.ARM_GET_TLS:
|
|
if hasattr(self, 'tls_value'):
|
|
cpu.regfile.write(ARM_REG_R0, self.tls_value)
|
|
elif cpu.PC == self.ARM_CMPXCHG:
|
|
oldval = cpu.regfile.read(ARM_REG_R0)
|
|
newval = cpu.regfile.read(ARM_REG_R1)
|
|
ptr = cpu.regfile.read(ARM_REG_R2)
|
|
|
|
existing = cpu.read_int(ptr, cpu.address_bit_size)
|
|
ret = 1
|
|
if existing == oldval:
|
|
ret = 0
|
|
cpu.regfile.write(ARM_REG_APSR_C, 1)
|
|
cpu.write_int(ptr, newval, cpu.address_bit_size)
|
|
|
|
cpu.regfile.write(ARM_REG_R0, ret)
|
|
elif cpu.PC == self.ARM_MEM_BARRIER:
|
|
# Apply any needed memory barrier to preserve consistency with data
|
|
# modified manually and __kuser_cmpxchg usage. Nop in our case, just
|
|
# return
|
|
pass
|
|
else:
|
|
raise e
|
|
|
|
# Return normally
|
|
lr = cpu.regfile.read(ARM_REG_R14) # ARM_REG_LR
|
|
cpu.PC = lr
|
|
|
|
|
|
def execute(self):
|
|
"""
|
|
Execute one cpu instruction in the current thread (only one suported).
|
|
@rtype: bool
|
|
@return: C{True}
|
|
|
|
@todo: This is where we could implement a simple schedule.
|
|
"""
|
|
syscallret = None
|
|
try:
|
|
self.current.execute()
|
|
self.clocks += 1
|
|
if self.clocks % 10000 == 0:
|
|
self.check_timers()
|
|
self.sched()
|
|
except Interruption, e:
|
|
try:
|
|
syscallret = self.int80(self.current)
|
|
except RestartSyscall:
|
|
pass
|
|
except Syscall, e:
|
|
try:
|
|
syscallret = self.syscall(self.current)
|
|
except RestartSyscall:
|
|
pass
|
|
except InvalidPCException, e:
|
|
self.handleInvalidPC(e)
|
|
|
|
return True
|
|
|
|
|
|
#64bit syscalls
|
|
|
|
def sys_fstat64(self, cpu, fd, buf):
|
|
'''
|
|
Determines information about a file based on its file descriptor (for Linux 64 bits).
|
|
@rtype: int
|
|
@param cpu: current CPU.
|
|
@param fd: the file descriptor of the file that is being inquired.
|
|
@param buf: a buffer where data about the file will be stored.
|
|
@return: C{0} on success.
|
|
@todo: Fix device number.
|
|
'''
|
|
''' unsigned long st_dev; /* Device. */
|
|
unsigned long st_ino; /* File serial number. */
|
|
unsigned int st_mode; /* File mode. */
|
|
unsigned int st_nlink; /* Link count. */
|
|
unsigned int st_uid; /* User ID of the file's owner. */
|
|
unsigned int st_gid; /* Group ID of the file's group. */
|
|
unsigned long st_rdev; /* Device number, if device. */
|
|
unsigned long __pad1;
|
|
long st_size; /* Size of file, in bytes. */
|
|
int st_blksize; /* Optimal block size for I/O. */
|
|
int __pad2;
|
|
long st_blocks; /* Number 512-byte blocks allocated. */
|
|
long st_atime; /* Time of last access. */
|
|
unsigned long st_atime_nsec;
|
|
long st_mtime; /* Time of last modification. */
|
|
unsigned long st_mtime_nsec;
|
|
long st_ctime; /* Time of last status change. */
|
|
unsigned long st_ctime_nsec;
|
|
unsigned int __unused4;
|
|
unsigned int __unused5;'''
|
|
stat = self.files[fd].stat()
|
|
|
|
bufstat = ''
|
|
bufstat += struct.pack('<Q', stat.st_dev)
|
|
bufstat += struct.pack('<Q', stat.st_ino)
|
|
bufstat += struct.pack('<L', stat.st_mode)
|
|
bufstat += struct.pack('<L', stat.st_nlink)
|
|
bufstat += struct.pack('<L', stat.st_uid)
|
|
bufstat += struct.pack('<L', stat.st_gid)
|
|
|
|
bufstat += struct.pack('<Q', 0)
|
|
bufstat += struct.pack('<Q', 0) #pad
|
|
|
|
bufstat += struct.pack('<Q', stat.st_size)
|
|
bufstat += struct.pack('<L', 1000 )
|
|
bufstat += struct.pack('<L', 0) #pad
|
|
|
|
bufstat += struct.pack('<Q', stat.st_size/512)
|
|
|
|
bufstat += struct.pack('d', stat.st_atime)
|
|
bufstat += struct.pack('<Q', 0)
|
|
bufstat += struct.pack('d', stat.st_mtime)
|
|
bufstat += struct.pack('<Q', 0)
|
|
bufstat += struct.pack('d', stat.st_ctime)
|
|
bufstat += struct.pack('<Q', 0)
|
|
bufstat += struct.pack('<L', 0) #pad
|
|
bufstat += struct.pack('<L', 0) #pad
|
|
|
|
cpu.write_bytes(buf, bufstat)
|
|
return 0
|
|
def sys_stat64(self, cpu, path, buf):
|
|
'''
|
|
Determines information about a file based on its filename (for Linux 64 bits).
|
|
@rtype: int
|
|
@param cpu: current CPU.
|
|
@param path: the pathname of the file that is being inquired.
|
|
@param buf: a buffer where data about the file will be stored.
|
|
@return: C{0} on success.
|
|
'''
|
|
fd = self.sys_open(cpu, path, 0, 'r')
|
|
ret = self.sys_fstat64(cpu, fd, buf)
|
|
self.sys_close(cpu, fd)
|
|
return ret
|
|
|
|
def _arch_reg_init(self, cpu, arch):
|
|
if arch in {'i386', 'amd64'}:
|
|
x86_defaults = {
|
|
'CS': 0x23,
|
|
'SS': 0x2b,
|
|
'DS': 0x2b,
|
|
'ES': 0x2b,
|
|
}
|
|
for reg, val in x86_defaults.iteritems():
|
|
cpu.regfile.write(reg, val)
|
|
|
|
############################################################################
|
|
# Symbolic versions follows
|
|
|
|
class SLinux(Linux):
|
|
'''
|
|
A symbolic extension of a Decree Operating System Model.
|
|
'''
|
|
def __init__(self, constraints, programs, argv, envp, symbolic_random=None, symbolic_files=()):
|
|
'''
|
|
Builds a symbolic extension of a Decree OS
|
|
@param constraints: a constraints.
|
|
@param cpus: CPU for this model.
|
|
@param mem: memory for this model.
|
|
'''
|
|
self._constraints = ConstraintSet()
|
|
self.random = 0
|
|
self.symbolic_files=symbolic_files
|
|
super(SLinux, self).__init__(programs, argv, envp)
|
|
|
|
def _mk_proc(self, arch):
|
|
if arch in {'i386', 'armv7'}:
|
|
mem = SMemory32(self.constraints)
|
|
else:
|
|
mem = SMemory64(self.constraints)
|
|
return CpuFactory.get_cpu(mem, arch)
|
|
|
|
@property
|
|
def constraints(self):
|
|
return self._constraints
|
|
|
|
#marshaling/pickle
|
|
def __getstate__(self):
|
|
state = super(SLinux, self).__getstate__()
|
|
state['constraints'] = self.constraints
|
|
state['random'] = self.random
|
|
state['symbolic_files'] = self.symbolic_files
|
|
return state
|
|
|
|
def __setstate__(self, state):
|
|
self._constraints = state['constraints']
|
|
self.random = state['random']
|
|
self.symbolic_files = state['symbolic_files']
|
|
super(SLinux, self).__setstate__(state)
|
|
|
|
#Distpatchers...
|
|
def syscall(self, cpu):
|
|
try:
|
|
return super(SLinux, self).syscall(cpu)
|
|
except SymbolicSyscallArgument, e:
|
|
reg_name = ['RDI', 'RSI', 'RDX', 'R10', 'R8', 'R9' ][e.reg_num]
|
|
raise ConcretizeRegister(reg_name,e.message,e.policy)
|
|
|
|
def int80(self, cpu):
|
|
try:
|
|
return super(SLinux, self).int80(cpu)
|
|
except SymbolicSyscallArgument, e:
|
|
reg_name = ['EBX', 'ECX', 'EDX', 'ESI', 'EDI', 'EBP' ][e.reg_num]
|
|
raise ConcretizeRegister(reg_name,e.message,e.policy)
|
|
|
|
|
|
|
|
def sys_read(self, cpu, fd, buf, count):
|
|
''' Symbolic version of Decree.sys_receive
|
|
'''
|
|
if isinstance(fd, Expression):
|
|
logger.debug("Ask to read from a symbolic file descriptor!!")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(0)
|
|
|
|
if isinstance(buf, Expression):
|
|
logger.debug("Ask to read to a symbolic buffer")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(1)
|
|
|
|
if isinstance(count, Expression):
|
|
logger.debug("Ask to read a symbolic number of bytes ")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(2)
|
|
|
|
return super(SLinux, self).sys_read(cpu, fd, buf, count)
|
|
|
|
|
|
def sys_fstat(self, cpu, fd, buf):
|
|
'''
|
|
Determines information about a file based on its file descriptor.
|
|
@rtype: int
|
|
@param cpu: current CPU.
|
|
@param fd: the file descriptor of the file that is being inquired.
|
|
@param buf: a buffer where data about the file will be stored.
|
|
@return: C{0} on success.
|
|
'''
|
|
'''
|
|
dev_t st_dev; /* ID of device containing file */
|
|
ino_t st_ino; /* inode number */
|
|
mode_t st_mode; /* protection */
|
|
nlink_t st_nlink; /* number of hard links */
|
|
uid_t st_uid; /* user ID of owner */
|
|
gid_t st_gid; /* group ID of owner */
|
|
dev_t st_rdev; /* device ID (if special file) */
|
|
off_t st_size; /* total size, in bytes */
|
|
blksize_t st_blksize; /* blocksize for file system I/O */
|
|
blkcnt_t st_blocks; /* number of 512B blocks allocated */
|
|
time_t st_atime; /* time of last access */
|
|
time_t st_mtime; /* time of last modification */
|
|
time_t st_ctime; /* time of last status change */
|
|
'''
|
|
stat = self.files[fd].stat()
|
|
bufstat = ''
|
|
bufstat += struct.pack('<L', stat.st_dev)
|
|
bufstat += struct.pack('<L', 0)
|
|
bufstat += struct.pack('<L', 0)
|
|
bufstat += struct.pack('<L', stat.st_ino)
|
|
bufstat += struct.pack('<L', stat.st_mode)
|
|
bufstat += struct.pack('<L', stat.st_nlink)
|
|
bufstat += struct.pack('<L', 0)
|
|
bufstat += struct.pack('<L', 0)
|
|
bufstat += struct.pack('<L', 0)
|
|
bufstat += struct.pack('<L', 0)
|
|
bufstat += struct.pack('<L', 0)
|
|
bufstat += struct.pack('<L', stat.st_size)
|
|
bufstat += struct.pack('<L', 0)
|
|
bufstat += struct.pack('<L', stat.st_blksize)
|
|
bufstat += struct.pack('<L', stat.st_blocks)
|
|
bufstat += struct.pack('<L', 0)
|
|
|
|
bufstat += struct.pack('d', stat.st_atime)
|
|
bufstat += struct.pack('d', stat.st_ctime)
|
|
bufstat += struct.pack('d', stat.st_mtime)
|
|
cpu.write_bytes(buf, bufstat)
|
|
return 0
|
|
|
|
def sys_mmap2(self, cpu, address, size, prot, flags, fd, offset):
|
|
'''
|
|
Creates a new mapping in the virtual address space of the calling process.
|
|
@rtype: int
|
|
@param cpu: current CPU.
|
|
@param address: the starting address for the new mapping. This address is used as hint unless the
|
|
flag contains C{MAP_FIXED}.
|
|
@param size: the length of the mapping.
|
|
@param prot: the desired memory protection of the mapping.
|
|
@param flags: determines whether updates to the mapping are visible to other
|
|
processes mapping the same region, and whether updates are carried
|
|
through to the underlying file.
|
|
@param fd: the contents of a file mapping are initialized using C{size} bytes starting at
|
|
offset C{offset} in the file referred to by the file descriptor C{fd}.
|
|
@param offset: the contents of a file mapping are initialized using C{size} bytes starting at
|
|
offset C{offset}*0x1000 in the file referred to by the file descriptor C{fd}.
|
|
@return:
|
|
- C{-1} In case you use C{MAP_FIXED} in the flags and the mapping can not be place at the desired address.
|
|
- the address of the new mapping.
|
|
'''
|
|
return self.sys_mmap(cpu, address, size, prot, flags, fd, offset*0x1000)
|
|
|
|
def sys_mmap(self, cpu, address, size, prot, flags, fd, offset):
|
|
'''
|
|
Creates a new mapping in the virtual address space of the calling process.
|
|
@rtype: int
|
|
|
|
@param cpu: current CPU.
|
|
@param address: the starting address for the new mapping. This address is used as hint unless the
|
|
flag contains C{MAP_FIXED}.
|
|
@param size: the length of the mapping.
|
|
@param prot: the desired memory protection of the mapping.
|
|
@param flags: determines whether updates to the mapping are visible to other
|
|
processes mapping the same region, and whether updates are carried
|
|
through to the underlying file.
|
|
@param fd: the contents of a file mapping are initialized using C{size} bytes starting at
|
|
offset C{offset} in the file referred to by the file descriptor C{fd}.
|
|
@param offset: the contents of a file mapping are initialized using C{size} bytes starting at
|
|
offset C{offset} in the file referred to by the file descriptor C{fd}.
|
|
@return:
|
|
- C{-1} in case you use C{MAP_FIXED} in the flags and the mapping can not be place at the desired address.
|
|
- the address of the new mapping (that must be the same as address in case you included C{MAP_FIXED} in flags).
|
|
@todo: handle exception.
|
|
'''
|
|
|
|
if address == 0:
|
|
address = None
|
|
if flags & 0x10 !=0 :
|
|
cpu.memory.munmap(address,size)
|
|
|
|
perms = perms_from_protflags(prot)
|
|
|
|
if (flags & 0x20 != 0):
|
|
result = cpu.memory.mmap(address, size, perms)
|
|
elif fd == 0:
|
|
assert offset == 0
|
|
result = cpu.memory.mmap(address, size, perms)
|
|
data = self.files[fd].read(size)
|
|
cpu.write_bytes(result,data)
|
|
else:
|
|
#FIXME Check if file should be symbolic input and do as with fd0
|
|
result = cpu.memory.mmapFile(address, size, perms, self.files[fd].name, offset)
|
|
|
|
if (flags & 0x10 !=0) and result != address:
|
|
cpu.memory.munmap(result, size)
|
|
result = -1
|
|
logger.debug("sys_mmap(0x%016x, 0x%x, %s, %x, %d) - (%r)", result, size, perms, flags, fd, prot)
|
|
return result
|
|
|
|
|
|
def sys_write(self, cpu, fd, buf, count):
|
|
''' Symbolic version of Decree.sys_receive
|
|
'''
|
|
if isinstance(fd, Expression):
|
|
logger.debug("Ask to write to a symbolic file descriptor!!")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(0)
|
|
|
|
if isinstance(buf, Expression):
|
|
logger.debug("Ask to write to a symbolic buffer")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(1)
|
|
|
|
if isinstance(count, Expression):
|
|
logger.debug("Ask to write a symbolic number of bytes ")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(2)
|
|
|
|
return super(SLinux, self).sys_write(cpu, fd, buf, count)
|
|
|
|
|
|
def sys_allocate(self, cpu, length, isX, address_p):
|
|
if isinstance(length, Expression):
|
|
logger.debug("Ask to ALLOCATE a symbolic number of bytes ")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(0)
|
|
if isinstance(address_p, Expression):
|
|
logger.debug("Ask to ALLOCATE potentially executable or not executable memory")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(1)
|
|
if isinstance(address_p, Expression):
|
|
logger.debug("Ask to return ALLOCATE result to a symbolic reference ")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(2)
|
|
return super(SLinux, self).sys_allocate(cpu, length, isX, address_p)
|
|
|
|
def sys_deallocate(self, cpu, addr, size):
|
|
if isinstance(addr, Expression):
|
|
logger.debug("Ask to DEALLOCATE a symbolic pointer?!")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(0)
|
|
if isinstance(size, Expression):
|
|
logger.debug("Ask to DEALLOCATE a symbolic size?!")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(1)
|
|
return super(SLinux, self).sys_deallocate(cpu, addr, size)
|
|
|
|
class DecreeEmu(object):
|
|
|
|
RANDOM = 0
|
|
|
|
@staticmethod
|
|
def cgc_initialize_secret_page(model):
|
|
logger.info("Skipping: cgc_initialize_secret_page()")
|
|
return 0
|
|
|
|
@staticmethod
|
|
def cgc_random(model, buf, count, rnd_bytes):
|
|
import cgcrandom
|
|
if isinstance(buf, Expression):
|
|
logger.info("Ask to write random bytes to a symbolic buffer")
|
|
raise ConcretizeArgument(0)
|
|
|
|
if isinstance(count, Expression):
|
|
logger.info("Ask to read a symbolic number of random bytes ")
|
|
raise ConcretizeArgument(1)
|
|
|
|
if isinstance(rnd_bytes, Expression):
|
|
logger.info("Ask to return rnd size to a symbolic address ")
|
|
raise ConcretizeArgument(2)
|
|
|
|
data = []
|
|
for i in xrange(count):
|
|
value = cgcrandom.stream[DecreeEmu.RANDOM]
|
|
data.append(value)
|
|
DecreeEmu.random += 1
|
|
|
|
cpu = model.current
|
|
cpu.write(buf, data)
|
|
if rnd_bytes:
|
|
cpu.store(rnd_bytes, len(data), 32)
|
|
logger.info("RANDOM(0x%08x, %d, 0x%08x) -> %d", buf, count, rnd_bytes, len(data))
|
|
return 0
|