#!/usr/bin/ruby # This file is part of Metasm, the Ruby assembly manipulation suite # Copyright (C) 2006-2009 Yoann GUILLOT # # Licence is LGPL, see LICENCE in the top-level directory # # this exemple illustrates the use of the PTrace class to hijack a syscall in a running process # the next syscall made is patched to run the syscall with the arguments of our choice, then # run the original intended syscall # Works on linux/x86 # require 'metasm' class SyscallHooker < Metasm::PTrace CTX = ['EBX', 'ECX', 'EDX', 'ESI', 'EDI', 'EAX', 'ESP', 'EBP', 'EIP', 'ORIG_EAX'] def inject(sysnr, *args) sysnr = SYSCALLNR[sysnr] || sysnr syscall puts '[*] waiting syscall' Process.waitpid(@pid) savedctx = CTX.inject({}) { |ctx, reg| ctx.update reg => peekusr(REGS_I386[reg]) } eip = (savedctx['EIP'] - 2) & 0xffffffff fu = readmem(eip, 2) if fu == "\xcd\x80" mode = :int80 elsif fu == "\xeb\xf3" and readmem(eip-14, 7).unpack('H*').first == "51525589e50f34" # aoenthuasn mode = :sysenter else puts 'unhandled syscall convention, aborting, code = ' + readmem(eip-4, 8).unpack('H*').first cont return self end if args.length > 5 puts 'too may arguments, unsupported, aborting' else puts "[*] hooking #{SYSCALLNR.index(savedctx['ORIG_EAX'])}" # stack pointer to store buffers to esp_ptr = savedctx['ESP'] write_string = lambda { |s| esp_ptr -= s.length esp_ptr &= 0xffff_fff0 writemem(esp_ptr, s) [esp_ptr].pack('L').unpack('l').first } set_arg = lambda { |a| case a when String; write_string[a + 0.chr] when Array; write_string[a.map { |aa| set_arg[aa] }.pack('L*')] else a end } args.zip(CTX).map { |arg, reg| # set syscall args, put buffers on the stack as needed pokeusr(REGS_I386[reg], set_arg[arg]) } # patch syscall number pokeusr(REGS_I386['ORIG_EAX'], sysnr) # run hooked syscall syscall Process.waitpid(@pid) retval = peekusr(REGS_I386['EAX']) puts "[*] retval: #{'%X' % retval}#{" (Errno::#{ERRNO.index(-retval)})" if retval < 0}" if SYSCALLNR.index(sysnr) == 'execve' and retval >= 0 cont return self end # restore eax & eip to run the orig syscall savedctx['EIP'] -= 2 savedctx['EAX'] = savedctx['ORIG_EAX'] savedctx.each { |reg, val| pokeusr(REGS_I386[reg], val) } end self end end if $0 == __FILE__ SyscallHooker.new(ARGV.shift.to_i).inject('write', 2, "testic\n", 7).detach end