def validate_integer_argument(pid, syscall_object, trace_arg, exec_arg, params=None): logging.debug( 'Validating integer argument (trace position: %d ' 'execution position: %d)', trace_arg, exec_arg) # EAX is the system call number POS_TO_REG = { 0: cint.EBX, 1: cint.ECX, 2: cint.EDX, 3: cint.ESI, 4: cint.EDI } if not params: arg = cint.peek_register(pid, POS_TO_REG[exec_arg]) else: arg = params[exec_arg] arg_from_trace = int(syscall_object.args[trace_arg].value) logging.debug('Argument from execution: %d', arg) logging.debug('Argument from trace: %d', arg_from_trace) # Check to make sure everything is the same # Decide if this is a system call we want to replay if arg_from_trace != arg: raise ReplayDeltaError('Argument value at trace position: {}, ' 'execution position: {} from execution ({}) ' 'differs argument value from trace ({})'.format( trace_arg, exec_arg, arg, arg_from_trace))
def subcall_return_success_handler(syscall_id, syscall_object, pid): '''This probably should be here. This badly named handler simply takes a socket subcall situation, validates the file descriptor involved, no-ops the call, and applies the return conditions from the system call object. For several socket calls, this is all that's required so we don't need to write individual handlers that all do the same thing. TODO: Move this to generic_handlers module TODO: Replace parameter extraction with call to extract_socketcall_parameters ''' logging.debug('Entering subcall return success handler') if syscall_object.ret[0] == -1: logging.debug('Handling unsuccessful call') else: logging.debug('Handling successful call') ecx = cint.peek_register(pid, cint.ECX) logging.debug('Extracting parameters from address %s', ecx) params = extract_socketcall_parameters(pid, ecx, 1) fd = params[0] fd_from_trace = syscall_object.args[0].value logging.debug('File descriptor from execution: %s', fd) logging.debug('File descriptor from trace: %s', fd_from_trace) if fd != int(fd_from_trace): raise ReplayDeltaError( 'File descriptor from execution ({}) ' 'differs from file descriptor from trace'.format( fd, fd_from_trace)) noop_current_syscall(pid) apply_return_conditions(pid, syscall_object)
def socketcall_handler(syscall_id, syscall_object, entering, pid): ''' Validate the subcall (NOT SYSCALL!) id of the socket subcall against the subcall name we expect based on the current system call object. Then, hand off responsibility to the appropriate subcall handler. TODO: rename to handle_socketcall and correct references as needed ''' subcall_handlers = { ('socket', True): socket_subcall_entry_handler, ('socket', False): socket_exit_handler, ('accept', True): accept_subcall_entry_handler, ('accept', False): accept_subcall_entry_handler, ('bind', True): bind_entry_handler, ('bind', False): bind_exit_handler, ('listen', True): listen_entry_handler, ('listen', False): listen_exit_handler, ('recv', True): recv_subcall_entry_handler, ('recvfrom', True): recvfrom_subcall_entry_handler, ('setsockopt', True): setsockopt_entry_handler, ('send', True): send_entry_handler, ('send', False): send_exit_handler, ('connect', True): connect_entry_handler, ('connect', False): connect_exit_handler, ('getsockopt', True): getsockopt_entry_handler, # ('sendmmsg', True): sendmmsg_entry_handler, ('sendto', True): sendto_entry_handler, ('sendto', False): sendto_exit_handler, ('shutdown', True): shutdown_subcall_entry_handler, ('recvmsg', True): recvmsg_entry_handler, ('recvmsg', False): recvmsg_exit_handler, ('getsockname', True): getsockname_entry_handler, ('getsockname', False): getsockname_exit_handler, ('getpeername', True): getpeername_entry_handler } # The subcall id of the socket subcall is located in the EBX register # according to our Linux's convention. subcall_id = cint.peek_register(pid, cint.EBX) validate_subcall(subcall_id, syscall_object) try: subcall_handlers[(syscall_object.name, entering)](syscall_id, syscall_object, pid) except KeyError: raise NotImplementedError('No handler for socket subcall %s %s', syscall_object.name, 'entry' if entering else 'exit')
def validate_address_argument(pid, syscall_object, trace_arg, exec_arg, params=None): logging.debug( 'Validating address argument (trace position: %d ' 'execution position: %d)', trace_arg, exec_arg) if not params: arg = cint.peek_register(pid, _pos_to_reg(exec_arg)) else: arg = params[exec_arg] # Convert signed interpretation from peek register to unsigned arg = arg & 0xffffffff arg_from_trace = int(syscall_object.args[trace_arg].value, 16) if arg_from_trace != arg: raise ReplayDeltaError('Argument value at trace position: {}, ' 'execution position: {} from execution ({}) ' 'differs argument value from trace ({})'.format( trace_arg, exec_arg, arg, arg_from_trace))
def noop_current_syscall(pid): ''''No-op' out the current system call the child process is trying to execute by replacing it with a call to getpid() (a system call that takes no parameters and has no side effects). Then, configure ptrace to allow the child process to run until it exits this call to getpid() and tell our own process to wait for this notification. Set the entering flip-flip flag to to show that we are exiting a system call (because the child application now believes the system call it tried to make completed successfully). When this function is called from a handler, the handler needs to deal with setting up the output buffers and return value that the system call would have done itself had we allowed it to run normally. Note: This function leaves the child process in a state of waiting at the point just before execution returns to userspace code. ''' logging.debug('Nooping the current system call in pid: %s', pid) # Transform the current system call in the child process into a call to # getpid() by poking 20 into ORIG_EAX cint.poke_register(pid, cint.ORIG_EAX, 20) # Tell ptrace we want the child process to stop at the next system call # event and restart its execution. cint.syscall(pid) # Have our process monitor the execution of the child process until it # receives a system call event notification. The notification we receive # at this point (if all goes according to plan) is the EXIT notification # for the getpid() call we forced the application to make. next_syscall() # Take a look at the current system call (i.e. the one that triggered the # notification we just received from ptrace). It should be getpid(). If # it isnt, something has gone horribly wrong and we must bail out. skipping = cint.peek_register(pid, cint.ORIG_EAX) if skipping != 20: raise Exception( 'Nooping did not result in getpid exit. Got {}'.format(skipping)) # Because we are exiting the getpid() call so we need to set the entering # flip-flop flag to reflect this. This allows later code (in main.py) to # set it BACK to entering before we begin processing the entry for the next # system call. tracereplay.entering_syscall = False
def swap_trace_fd_to_execution_fd(pid, pos, syscall_object, params_addr=None): POS_TO_REG = { 0: cint.EBX, 1: cint.ECX, 2: cint.EDX, 3: cint.ESI, 4: cint.EDI, } logging.debug('Cleaning up file descriptor at position: {}'.format(pos)) trace_fd = int(syscall_object.args[pos].value) looked_up_fd = fd_pair_for_trace_fd(trace_fd)['os_fd'] if params_addr: params = extract_socketcall_parameters(pid, params_addr, pos + 1) execution_fd = params[pos] else: execution_fd = cint.peek_register(pid, POS_TO_REG[pos]) logging.debug( 'Replacing old value (trace fd): {} with new value: {}'.format( execution_fd, looked_up_fd)) if params_addr: update_socketcall_paramater(pid, params_addr, pos, looked_up_fd) else: cint.poke_register(pid, POS_TO_REG[pos], looked_up_fd)
def handle_syscall(syscall_id, syscall_object, entering, pid): ''' Validate the id of the system call against the name of the system call we are expecting based on the current system call object. Then hand off responsiblity to the appropriate subcall handler. TODO: cosmetic - Reorder handler entrys numerically ''' logging.debug('Handling syscall') # If we are entering a system call, update the number of system calls we # have handled if entering: tracereplay.handled_syscalls += 1 # System call id 102 corresponds to 'socket subcall'. This system call is # the entry point for code calls the appropriate socketf code based on the # subcall id in EBX. if syscall_id == 102: logging.debug('This is a socket subcall') # TODO: delete this logging ebx = cint.peek_register(pid, cint.EBX) logging.debug('Socketcall id from EBX is: %s', ebx) # Hand off to code that deals with socket calls and return once that is # complete. Exceptions will be thrown if something is unsuccessful # that end. Return immediately after because we don't want our system # call handler code double-handling the already handled socket subcall socketcall_handler(syscall_id, syscall_object, entering, pid) return logging.debug('Checking syscall against execution') validate_syscall(orig_eax, syscall_object) # We ignore these system calls because they have to do with aspecs of # execution that we don't want to try to replay and, at the same time, # don't have interesting information that we want to validate with a # handler. ignore_list = [ 77, # sys_getrusage 162, # sys_nanosleep 125, # sys_mprotect 175, # sys_rt_sigprocmask 116, # sys_sysinfo 119, # sys_sigreturn 126, # sys_sigprocmask 186, # sys_sigaltstack 266, # set_clock_getres 240, # sys_futex 242, # sys_sched_getaffinity 243, # sys_set_thread_area 311, # sys_set_robust_list 340, # sys_prlimit64 191, # !!!!!!!!! sys_getrlimit ] handlers = { (8, True): creat_entry_handler, (8, False): check_return_value_exit_handler, # These calls just get their return values checked #### # (9, True): check_return_value_entry_handler, # (9, False): check_return_value_exit_handler, (12, True): syscall_return_success_handler, # (195, True): check_return_value_entry_handler, # (195, False): check_return_value_exit_handler, (39, True): check_return_value_entry_handler, (39, False): check_return_value_exit_handler, (45, True): check_return_value_entry_handler, (45, False): check_return_value_exit_handler, (91, True): check_return_value_entry_handler, (91, False): check_return_value_exit_handler, # (125, True): check_return_value_entry_handler, # (125, False): check_return_value_exit_handler, # mmap2 calls are never replayed. Sometimes we must fix a file # descriptor in position 4. (192, True): mmap2_entry_handler, (192, False): mmap2_exit_handler, (196, True): lstat64_entry_handler, (10, True): unlink_entry_handler, (10, False): check_return_value_exit_handler, (20, True): syscall_return_success_handler, (30, True): syscall_return_success_handler, (38, True): rename_entry_handler, (38, False): check_return_value_exit_handler, (15, True): syscall_return_success_handler, (78, True): gettimeofday_entry_handler, (13, True): time_entry_handler, (27, True): syscall_return_success_handler, (5, True): open_entry_handler, (5, False): open_exit_handler, (60, True): syscall_return_success_handler, (85, True): readlink_entry_handler, (93, True): ftruncate_entry_handler, (93, False): ftruncate_exit_handler, (94, True): fchmod_entry_handler, (94, False): check_return_value_entry_handler, (145, True): readv_entry_handler, (145, False): check_return_value_exit_handler, (146, True): writev_entry_handler, (146, False): writev_exit_handler, (197, True): fstat64_entry_handler, (197, False): check_return_value_exit_handler, (122, True): uname_entry_handler, (183, True): getcwd_entry_handler, (140, True): llseek_entry_handler, (140, False): llseek_exit_handler, (42, True): pipe_entry_handler, # (43, True): times_entry_handler, # (10, True): syscall_return_success_handler, (33, True): syscall_return_success_handler, (199, True): syscall_return_success_handler, (200, True): syscall_return_success_handler, (201, True): syscall_return_success_handler, (202, True): syscall_return_success_handler, (4, True): write_entry_handler, (4, False): write_exit_handler, (3, True): read_entry_handler, (3, False): check_return_value_exit_handler, (6, True): close_entry_handler, (6, False): close_exit_handler, (168, True): poll_entry_handler, (54, True): ioctl_entry_handler, (54, False): ioctl_exit_handler, (195, True): stat64_entry_handler, (195, False): check_return_value_exit_handler, (141, True): getdents_entry_handler, (142, False): getdents_exit_handler, (142, True): select_entry_handler, (82, True): select_entry_handler, (221, True): fcntl64_entry_handler, (196, True): lstat64_entry_handler, (268, True): statfs64_entry_handler, (265, True): clock_gettime_entry_handler, (41, True): dup_entry_handler, (41, False): dup_exit_handler, (150, True): syscall_return_success_handler, (174, True): rt_sigaction_entry_handler, (186, True): sigaltstack_entry_handler, (194, True): ftruncate64_entry_handler, (194, False): ftruncate64_entry_handler, (207, True): fchown_entry_handler, (207, False): check_return_value_entry_handler, (209, True): getresuid_entry_handler, (211, True): getresgid_entry_handler, (220, True): getdents64_entry_handler, (220, False): getdents64_exit_handler, (228, True): fsetxattr_entry_handler, (228, False): fsetxattr_exit_handler, (231, True): fgetxattr_entry_handler, (231, False): fgetxattr_exit_handler, (234, True): flistxattr_entry_handler, (234, False): flistxattr_entry_handler, (242, True): sched_getaffinity_entry_handler, (243, True): syscall_return_success_handler, (258, True): set_tid_address_entry_handler, (258, False): set_tid_address_exit_handler, (259, True): timer_create_entry_handler, (260, True): timer_settime_entry_handler, (261, True): timer_gettime_entry_handler, (263, True): timer_delete_entry_handler, (265, True): clock_gettime_entry_handler, (271, True): syscall_return_success_handler, (272, True): fadvise64_64_entry_handler, (272, False): check_return_value_exit_handler, (295, True): openat_entry_handler, (295, False): openat_exit_handler, (300, True): fstatat64_entry_handler, (300, False): check_return_value_exit_handler, (301, True): unlinkat_entry_handler, (301, False): check_return_value_exit_handler, (311, True): syscall_return_success_handler, (320, True): utimensat_entry_handler, (320, False): check_return_value_exit_handler, (328, True): eventfd2_entry_handler, (340, True): prlimit64_entry_handler, (345, True): sendmmsg_entry_handler, (345, False): sendmmsg_exit_handler } if syscall_id not in ignore_list: try: handlers[(syscall_id, entering)](syscall_id, syscall_object, pid) except KeyError: raise NotImplementedError('Encountered un-ignored syscall {} ' 'with no handler: {}({})'.format( 'entry' if entering else 'exit', syscall_id, syscall_object.name))
t = Trace.Trace(trace) tracereplay.system_calls = t.syscalls logging.info('Parsed trace with %s syscalls', len(t.syscalls)) logging.info('Entering syscall handling loop') # Loop until we are no longer receiving syscall notifications from our # ptrace session with the child process. while next_syscall(): # Get the system call id of the current system call. Convention in # our flavor of Linux is for this to be passed in the EAX # register (ORIG_EAX, in ptrace terms). Ptrace does not inform us # of whether the current system call action we have been notified # of is an entry or exit so we operate on the assumption that the # first notification we receive is an entry, the next is an exit, # the next is an entry etc. orig_eax = cint.peek_register(pid, cint.ORIG_EAX) logging.info('===') logging.info('Advanced to next system call') logging.info('System call id from execution: %d', orig_eax) logging.info('Looked up system call name: %s', SYSCALLS[orig_eax]) logging.info('This is a system call %s', 'entry' if tracereplay.entering_syscall else 'exit') # This if statement is an ugly hack. We skip these system calls # out of sequence because they do not result in a corresponding # 'system call exit' notification from ptrace meaning they throw # off the pattern of 'entry', 'exit', 'entry', 'exit'... that we # rely on for determining whether the ptrace message we have # received is a system call entry or exit. if SYSCALLS[orig_eax] == 'sys_exit_group' or \ SYSCALLS[orig_eax] == 'sys_execve' or \ SYSCALLS[orig_eax] == 'sys_exit':