From 50fd50ee1206d356d11864e08b4377cb99697960 Mon Sep 17 00:00:00 2001 From: Yan Date: Fri, 5 May 2017 13:11:59 -0400 Subject: [PATCH] Simplify linux model interfaces (#233) --- manticore/models/linux.py | 278 ++++++++++++++++++-------------------- tests/test_linux.py | 4 +- 2 files changed, 132 insertions(+), 150 deletions(-) diff --git a/manticore/models/linux.py b/manticore/models/linux.py index 3811c07..ba060b9 100644 --- a/manticore/models/linux.py +++ b/manticore/models/linux.py @@ -302,22 +302,19 @@ class Linux(object): 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_specific_init(cpu, arch) - self._stack_top = cpu.STACK - self.setup_stack(cpu, [program]+argv, envp) + self.procs = [self._mk_proc(arch)] - - self.procs.append(cpu) + self._current = 0 + self.load(program) + self._arch_specific_init(arch) + self._stack_top = self.current.STACK + self.setup_stack([program]+argv, envp) 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 @@ -416,20 +413,20 @@ class Linux(object): if '_arm_tls_memory' in state: self._arm_tls_memory = state['_arm_tls_memory'] - def _read_string(self, cpu, buf): + def _read_string(self, 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)) + c = Operators.CHR(self.current.read_int(buf + i, 8)) if c == '\x00': break filename += c return filename - def _init_arm_kernel_helpers(self, cpu): + def _init_arm_kernel_helpers(self): ''' ARM kernel helpers @@ -482,7 +479,7 @@ class Linux(object): ).decode('hex') # Map a TLS segment - self._arm_tls_memory = cpu.memory.mmap(None, 4, 'rw ') + self._arm_tls_memory = self.current.memory.mmap(None, 4, 'rw ') __kuser_get_tls = ( '04009FE5' + # ldr r0, [pc, #4] @@ -506,7 +503,7 @@ class Linux(object): update(0xff0, tls_area) update(0xffc, version) - cpu.memory.mmap(0xffff0000, len(page_data), 'r x', page_data) + self.current.memory.mmap(0xffff0000, len(page_data), 'r x', page_data) def load_vdso(self, bits): #load vdso #TODO or #IGNORE @@ -519,7 +516,7 @@ class Linux(object): return vdso_addr - def setup_stack(self, cpu, argv, envp): + def setup_stack(self, argv, envp): ''' :param Cpu cpu: The cpu instance :param argv: list of parameters for the program to execute. @@ -555,6 +552,7 @@ class Linux(object): (0xc0000000) < top of stack > 0 (virtual) ---------------------------------------------------------------------- ''' + cpu = self.current # In case setup_stack() is called again, we make sure we're growing the # stack from the original top @@ -654,7 +652,7 @@ class Linux(object): push_int(len(argvlst)) - def load(self, cpu, filename): + def load(self, 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. @@ -666,6 +664,7 @@ class Linux(object): ''' #load elf See binfmt_elf.c #read the ELF object file + cpu = self.current 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()] @@ -945,7 +944,7 @@ class Linux(object): return fd >= 0 and fd < len(self.files) and self.files[fd] is not None - def sys_lseek(self, cpu, fd, offset, whence): + def sys_lseek(self, fd, offset, whence): ''' lseek - reposition read/write file offset @@ -978,7 +977,7 @@ class Linux(object): logger.debug("LSEEK(%d, 0x%08x, %d)"%(fd, offset, whence)) return 0 - def sys_read(self, cpu, fd, buf, count): + def sys_read(self, fd, buf, count): data = '' if count != 0: if not self._is_open(fd): @@ -986,7 +985,7 @@ class Linux(object): return errno.EBADF # TODO check count bytes from buf - if not buf in cpu.memory: # or not cpu.memory.isValid(buf+count): + if not buf in self.current.memory: # or not self.current.memory.isValid(buf+count): logger.info("READ: buf points to invalid address. Returning EFAULT") return errno.EFAULT @@ -996,18 +995,17 @@ class Linux(object): # Read the data and put in tin memory data = self.files[fd].read(count) self.syscall_trace.append(("_read", fd, data)) - cpu.write_bytes(buf, data) + self.current.write_bytes(buf, data) logger.debug("READ(%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): + def sys_write(self, fd, buf, count): ''' write - send bytes through a file descriptor The write system call writes up to count bytes from the buffer pointed to by buf to the file descriptor fd. If count is zero, write returns 0 and optionally sets *tx_bytes to zero. - :param cpu current CPU :param fd a valid file descriptor :param buf a memory buffer :param count number of bytes to send @@ -1016,6 +1014,7 @@ class Linux(object): EFAULT buf or tx_bytes points to an invalid address. ''' data = [] + cpu = self.current if count != 0: if not self._is_open(fd): @@ -1042,12 +1041,11 @@ class Linux(object): return len(data) - def sys_access(self, cpu, buf, mode): + def sys_access(self, 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: @@ -1056,7 +1054,7 @@ class Linux(object): ''' filename = "" for i in xrange(0,255): - c = Operators.CHR(cpu.read_int(buf + i, 8)) + c = Operators.CHR(self.current.read_int(buf + i, 8)) if c == '\x00': break filename += c @@ -1071,11 +1069,10 @@ class Linux(object): else: return -1 - def sys_uname(self, cpu, old_utsname): + def sys_uname(self, 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 ''' @@ -1088,15 +1085,14 @@ class Linux(object): 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) + self.current.write_bytes(old_utsname, uname) logger.debug("sys_uname(...) -> %s", uname) return 0 - def sys_brk(self, cpu, brk): + def sys_brk(self, 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}. :raises error: @@ -1104,21 +1100,21 @@ class Linux(object): ''' if brk != 0: assert brk > self.elf_brk + mem = self.current.memory size = brk-self.elf_brk - perms = cpu.memory.perms(self.elf_brk-1) - if brk > cpu.memory._ceil(self.elf_brk): - addr = cpu.memory.mmap(cpu.memory._ceil(self.elf_brk), size, perms) - assert cpu.memory._ceil(self.elf_brk) == addr, "Error in brk!" + perms = mem.perms(self.elf_brk-1) + if brk > mem._ceil(self.elf_brk): + addr = mem.mmap(mem._ceil(self.elf_brk), size, perms) + assert mem._ceil(self.elf_brk) == 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): + def sys_arch_prctl(self, 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 @@ -1130,23 +1126,23 @@ class Linux(object): ARCH_GET_FS = 0x1003 ARCH_GET_GS = 0x1004 assert code == ARCH_SET_FS - cpu.FS=0x63 - cpu.set_descriptor(cpu.FS, addr, 0x4000, 'rw') + self.current.FS=0x63 + self.current.set_descriptor(self.current.FS, addr, 0x4000, 'rw') logger.debug("sys_arch_prctl(%04x, %016x) -> 0", code, addr) return 0 - def sys_ioctl(self, cpu, fd, request, argp): + def sys_ioctl(self, fd, request, argp): if fd > 2: return self.files[fd].ioctl(request, argp) else: return 0 - def sys_open(self, cpu, buf, flags, mode): + def sys_open(self, buf, flags, mode): # buf: address of zero-terminated pathname # flags/access: file access bits # perms: file permission mode - filename = self._read_string(cpu, buf) + filename = self._read_string(buf) try : if os.path.abspath(filename).startswith('/proc/self'): if filename == '/proc/self/exe': @@ -1167,21 +1163,21 @@ class Linux(object): return self._open(f) - def sys_getpid(self, cpu, v): + def sys_getpid(self, v): logger.debug("GETPID, warning pid modeled as concrete 1000") return 1000 - def sys_ARM_NR_set_tls(self, cpu, val): + def sys_ARM_NR_set_tls(self, val): if hasattr(self, '_arm_tls_memory'): - cpu.write_int(self._arm_tls_memory, val) + self.current.write_int(self._arm_tls_memory, val) return 0 #Signals.. - def sys_kill(self, cpu, pid, sig): + def sys_kill(self, pid, sig): logger.debug("KILL, Ignoring Sending signal %d to pid %d", sig, pid ) return 0 - def sys_sigaction(self, cpu, signum, act, oldact): + def sys_sigaction(self, signum, act, oldact): logger.debug("SIGACTION, Ignoring changing signal handler for signal %d", signum) return 0 @@ -1189,11 +1185,10 @@ class Linux(object): logger.debug("SIGACTION, Ignoring changing signal mask set cmd:%d", how) return 0 - def sys_close(self, cpu, fd): + def sys_close(self, fd): ''' Closes a file descriptor :rtype: int - :param cpu: current CPU. :param fd: the file descriptor to close. :return: C{0} on success. ''' @@ -1202,12 +1197,11 @@ class Linux(object): logger.debug('sys_close(%d)', fd) return 0 - def sys_readlink(self, cpu, path, buf, bufsize): + def sys_readlink(self, 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. @@ -1215,90 +1209,85 @@ class Linux(object): ''' if bufsize <= 0: return -errno.EINVAL - filename = self._read_string(cpu, path) + filename = self._read_string(path) data = os.readlink(filename)[:bufsize] - cpu.write_bytes(buf, data) + self.current.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): + def sys_mprotect(self, 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 access permission for the memory. :return: C{0} on success. ''' perms = perms_from_protflags(prot) - ret = cpu.memory.mprotect(start, size, perms) + ret = self.current.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): + def sys_munmap(self, 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) + self.current.memory.munmap(addr, size) return 0 - def sys_getuid(self, cpu): + def sys_getuid(self): ''' 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): + def sys_getgid(self): ''' 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): + def sys_geteuid(self): ''' 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): + def sys_getegid(self): ''' Gets group identity. :rtype: int - :param cpu: current CPU. :return: this call returns C{1000} for all the groups. ''' return 1000 - def sys_readv(self, cpu, fd, iov, count): + + def sys_readv(self, fd, iov, count): ''' Works just like C{sys_read} except that data is read into multiple buffers. :rtype: int - :param cpu: current CPU. :param fd: the file descriptor of the file to read. :param iov: the buffer where the the bytes to read are stored. :param count: amount of C{iov} buffers to read from the file. :return: the amount of bytes read in total. ''' + cpu = self.current ptrsize = cpu.address_bit_size sizeof_iovec = 2 * (ptrsize // 8) total = 0 @@ -1313,17 +1302,17 @@ class Linux(object): logger.debug("READV(%r, %r, %r) -> <%r> (size:%r)"%(fd, buf, size, data, len(data))) return total - def sys_writev(self, cpu, fd, iov, count): + def sys_writev(self, fd, iov, count): ''' Works just like C{sys_write} except that multiple buffers are written out. :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. ''' + cpu = self.current ptrsize = cpu.address_bit_size sizeof_iovec = 2 * (ptrsize // 8) total = 0 @@ -1340,27 +1329,26 @@ class Linux(object): total+=size return total - def sys_set_thread_area32(self, cpu, user_info): + def sys_set_thread_area32(self, 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) + n = self.current.read_int(user_info, 32) + pointer = self.current.read_int(user_info + 4, 32) + m = self.current.read_int(user_info + 8, 32) + flags = self.current.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) + self.current.GS=0x63 + self.current.set_descriptor(self.current.GS, pointer, 0x4000, 'rw') + self.current.write_int(user_info, (0x63 - 3) / 8, 32) return 0 - def sys_getpriority(self, cpu, which, who): + def sys_getpriority(self, which, who): ''' System call ignored. :rtype: int @@ -1370,7 +1358,7 @@ class Linux(object): logger.debug("Ignoring sys_get_priority") return 0 - def sys_setpriority(self, cpu, which, who, prio): + def sys_setpriority(self, which, who, prio): ''' System call ignored. :rtype: int @@ -1380,7 +1368,7 @@ class Linux(object): logger.debug("Ignoring sys_set_priority") return 0 - def sys_acct(self, cpu, path): + def sys_acct(self, path): ''' System call not implemented. :rtype: int @@ -1390,13 +1378,12 @@ class Linux(object): logger.debug("BSD account not implemented!") return -1 - def sys_exit_group(self, cpu, error_code): + def sys_exit_group(self, error_code): ''' Exits all threads in a process - :param cpu: current CPU. :raises Exception: 'Finished' ''' - procid = self.procs.index(cpu) + procid = self.procs.index(self.current) self.sched() self.running.remove(procid) #self.procs[procid] = None @@ -1405,41 +1392,40 @@ class Linux(object): raise ProcessExit(error_code) return error_code - def sys_ptrace(self, cpu, request, pid, addr, data): + def sys_ptrace(self, 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): + def sys_nanosleep(self, req, rem): logger.debug("sys_nanosleep(...)") return 0 - def sys_set_tid_address(self, cpu, tidptr): + def sys_set_tid_address(self, 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) + def sys_faccessat(self, dirfd, pathname, mode, flags): + filename = self._read_string(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): + def sys_set_robust_list(self, 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): + def sys_futex(self, uaddr, op, val, timeout, uaddr2, val3): logger.debug("sys_futex(...) -> -1") return -1 - def sys_getrlimit(self, cpu, resource, rlim): + def sys_getrlimit(self, resource, rlim): logger.debug("sys_getrlimit(%x, %x) -> -1",resource, rlim) return -1 - def sys_fadvise64(self, cpu, fd, offset, length, advice): + def sys_fadvise64(self, 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): + def sys_gettimeofday(self, tv, tz): logger.debug("sys_gettimeofday(%x, %x) -> 0", tv, tz) return 0 #Distpatchers... - def syscall(self, cpu): + def syscall(self): ''' 64 bit dispatcher. - :param cpu: current CPU. ''' syscalls = { 0x0000000000000008: self.sys_lseek, @@ -1498,25 +1484,25 @@ class Linux(object): } - index, arguments, writeResult = cpu.get_syscall_description() + + index, arguments, writeResult = self.current.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]) + result = func(*arguments[:nargs-1]) writeResult(result) return result - def int80(self, cpu): + def int80(self): ''' 32 bit dispatcher. - :param cpu: current CPU. ''' syscalls = { 0x00000001: self.sys_exit_group, 0x00000003: self.sys_read, @@ -1554,7 +1540,7 @@ class Linux(object): 0x00000014: self.sys_getpid, 0x000f0005: self.sys_ARM_NR_set_tls, } - index, arguments, writeResult = cpu.get_syscall_description() + index, arguments, writeResult = self.current.get_syscall_description() if index not in syscalls: raise SyscallNotImplemented(64, index) @@ -1563,21 +1549,20 @@ class Linux(object): 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]) + result = func(*arguments[:nargs-1]) writeResult(result) return result - def sys_clock_gettime(self, cpu, clock_id, timespec): + def sys_clock_gettime(self, clock_id, timespec): logger.info("sys_clock_time not really implemented") return 0 - def sys_time(self, cpu, tloc): + def sys_time(self, tloc): import time t = time.time() if tloc != 0 : - cpu.write_int(tloc, int(t), cpu.address_bit_size) + self.current.write_int(tloc, int(t), self.current.address_bit_size) return int(t) @@ -1709,25 +1694,25 @@ class Linux(object): self.sched() except Interruption, e: try: - syscallret = self.int80(self.current) + syscallret = self.int80() except RestartSyscall: pass except Syscall, e: try: - syscallret = self.syscall(self.current) + syscallret = self.syscall() except RestartSyscall: pass + return True #64bit syscalls - def sys_fstat64(self, cpu, fd, buf): + def sys_fstat64(self, 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. @@ -1781,35 +1766,34 @@ class Linux(object): bufstat += struct.pack('