class Metasm::PTrace

Constants

BTS
BTS_O

block trace

COMMAND

linux/ptrace.h

ERRNO

include/asm-generic/errno-base.h

OPTIONS
REGS_I386
REGS_X86_64
SIGINFO
SIGINFO_C
SIGNAL
SYSCALLNR_I386
SYSCALLNR_X86_64
WAIT_EXTENDEDRESULT

Attributes

buf[RW]
host_intsize[RW]
host_syscallnr[RW]
intsize[RW]
packint[RW]
packuint[RW]
pid[RW]
reg_off[RW]
syscallnr[RW]
syscallreg[RW]
tgcpu[RW]

Public Class Methods

new(target, do_attach=true, &b) click to toggle source

creates a ptraced process (target = path) or opens a running process (target = pid) values for do_attach:

:create => always fork+traceme+exec+wait
:attach => always attach
false/nil => same as attach, without actually calling PT_ATTACH (useful when the ruby process is already tracing pid)
default/anything else: try to attach if pid is numeric, else create
# File metasm/os/linux.rb, line 36
def initialize(target, do_attach=true, &b)
        case do_attach
        when :create
                init_create(target, &b)
        when :attach
                init_attach(target)
        when :dup
                raise ArgumentError unless target.kind_of?(PTrace)
                @pid = target.pid
                tweak_for_pid(@pid, target.tgcpu)            # avoid re-parsing /proc/self/exe
        when nil, false
                @pid = Integer(target)
                tweak_for_pid(@pid)
        else
                t = begin; Integer(target)
                    rescue ArgumentError, TypeError
                    end
                t ? init_attach(t) : init_create(target, &b)
        end
end
open(target) { |ptrace| ... } click to toggle source
# File metasm/os/linux.rb, line 14
def self.open(target)
        ptrace = new(target)
        return ptrace if not block_given?
        begin
                yield ptrace
        ensure
                ptrace.detach
        end
end
traceme() click to toggle source

calls PTRACE_TRACEME on the current (ruby) process

# File metasm/os/linux.rb, line 25
def self.traceme
        new(::Process.pid, false).traceme
end

Public Instance Methods

attach() click to toggle source
# File metasm/os/linux.rb, line 568
def attach
        sys_ptrace(COMMAND[:ATTACH], @pid, 0, 0)
end
bufval() click to toggle source

interpret the value turned as an unsigned long

# File metasm/os/linux.rb, line 176
def bufval
        @buf.unpack(@packint).first
end
cont(sig = nil) click to toggle source
# File metasm/os/linux.rb, line 544
def cont(sig = nil)
        sig ||= 0
        sys_ptrace(COMMAND[:CONT], @pid, 0, sig)
end
cp() click to toggle source
# File metasm/os/linux.rb, line 587
def cp
        @cp ||= @tgcpu.new_cparser
end
detach() click to toggle source
# File metasm/os/linux.rb, line 572
def detach
        sys_ptrace(COMMAND[:DETACH], @pid, 0, 0)
end
dup() click to toggle source
# File metasm/os/linux.rb, line 167
def dup
        self.class.new(self, :dup)
end
get_thread_area(addr) click to toggle source
# File metasm/os/linux.rb, line 532
def get_thread_area(addr)
        sys_ptrace(COMMAND[:GET_THREAD_AREA],  @pid, addr, @buf)
        bufval
end
geteventmsg() click to toggle source

retrieve pid of cld for EVENT_CLONE/FORK, exitcode for EVENT_EXIT

# File metasm/os/linux.rb, line 582
def geteventmsg
        sys_ptrace(COMMAND[:GETEVENTMSG], @pid, 0, @buf)
        bufval
end
getfpregs(buf=nil) click to toggle source
# File metasm/os/linux.rb, line 510
def getfpregs(buf=nil)
        buf = buf.str if buf.respond_to?(:str)
        buf ||= [0].pack('C')*1024
        sys_ptrace(COMMAND[:GETFPREGS], @pid, 0, buf)
        buf
end
getfpxregs(buf=nil) click to toggle source
# File metasm/os/linux.rb, line 521
def getfpxregs(buf=nil)
        buf = buf.str if buf.respond_to?(:str)
        buf ||= [0].pack('C')*512
        sys_ptrace(COMMAND[:GETFPXREGS], @pid, 0, buf)
        buf
end
getregs(buf=nil) click to toggle source
# File metasm/os/linux.rb, line 499
def getregs(buf=nil)
        buf = buf.str if buf.respond_to?(:str)        # AllocCStruct
        buf ||= [0].pack('C')*512
        sys_ptrace(COMMAND[:GETREGS], @pid, 0, buf)
        buf
end
getsiginfo() click to toggle source
# File metasm/os/linux.rb, line 598
def getsiginfo
        sys_ptrace(COMMAND[:GETSIGINFO], @pid, 0, siginfo.str)
        siginfo
end
host_csn() click to toggle source
# File metasm/os/linux.rb, line 165
def host_csn; @@host_csn end
init_attach(target) click to toggle source
# File metasm/os/linux.rb, line 57
def init_attach(target)
        @pid = Integer(target)
        tweak_for_pid(@pid)
        attach
        wait
        puts "Ptrace: attached to #@pid" if $DEBUG
end
init_create(target, &b) click to toggle source
# File metasm/os/linux.rb, line 65
def init_create(target, &b)
        if not @pid = ::Process.fork
                tweak_for_pid(::Process.pid)
                traceme
                b.call if b
                ::Process.exec(*target)
                exit!(0)
        end
        wait
        raise "could not exec #{target}" if $?.exited?
        tweak_for_pid(@pid)
        puts "Ptrace: attached to new #@pid" if $DEBUG
end
kill() click to toggle source
# File metasm/os/linux.rb, line 549
def kill
        sys_ptrace(COMMAND[:KILL], @pid, 0, 0)
end
peekdata(addr) click to toggle source
# File metasm/os/linux.rb, line 476
def peekdata(addr)
        sys_ptrace(COMMAND[:PEEKDATA], @pid, addr, @buf)
        @buf
end
peektext(addr) click to toggle source
# File metasm/os/linux.rb, line 471
def peektext(addr)
        sys_ptrace(COMMAND[:PEEKTEXT], @pid, addr, @buf)
        @buf
end
peekusr(addr) click to toggle source
# File metasm/os/linux.rb, line 481
def peekusr(addr)
        sys_ptrace(COMMAND[:PEEKUSR],  @pid, @host_intsize*addr, @buf)
        @peekmask ||= (1 << ([@host_intsize, @intsize].min*8)) - 1
        bufval & @peekmask
end
pokedata(addr, data) click to toggle source
# File metasm/os/linux.rb, line 491
def pokedata(addr, data)
        sys_ptrace(COMMAND[:POKEDATA], @pid, addr, data.unpack(@packint).first)
end
poketext(addr, data) click to toggle source
# File metasm/os/linux.rb, line 487
def poketext(addr, data)
        sys_ptrace(COMMAND[:POKETEXT], @pid, addr, data.unpack(@packint).first)
end
pokeusr(addr, data) click to toggle source
# File metasm/os/linux.rb, line 495
def pokeusr(addr, data)
        sys_ptrace(COMMAND[:POKEUSR],  @pid, @host_intsize*addr, data)
end
prctl(addr, data) click to toggle source
# File metasm/os/linux.rb, line 540
def prctl(addr, data)
        sys_ptrace(COMMAND[:ARCH_PRCTL], @pid, addr, data)
end
readmem(off, len) click to toggle source

reads a memory range

# File metasm/os/linux.rb, line 181
def readmem(off, len)
        decal = off % @host_intsize
        buf = ''
        if decal > 0
                off -= decal
                peekdata(off)
                off += @host_intsize
                buf << @buf[decal...@host_intsize]
        end
        offend = off + len
        while off < offend
                peekdata(off)
                buf << @buf[0, @host_intsize]
                off += @host_intsize
        end
        buf[0, len]
end
set_thread_area(addr, data) click to toggle source
# File metasm/os/linux.rb, line 536
def set_thread_area(addr, data)
        sys_ptrace(COMMAND[:SET_THREAD_AREA],  @pid, addr, data)
end
setfpregs(buf) click to toggle source
# File metasm/os/linux.rb, line 516
def setfpregs(buf)
        buf = buf.str if buf.respond_to?(:str)
        sys_ptrace(COMMAND[:SETFPREGS], @pid, 0, buf)
end
setfpxregs(buf) click to toggle source
# File metasm/os/linux.rb, line 527
def setfpxregs(buf)
        buf = buf.str if buf.respond_to?(:str)
        sys_ptrace(COMMAND[:SETFPXREGS], @pid, 0, buf)
end
setoptions(*opt) click to toggle source
# File metasm/os/linux.rb, line 576
def setoptions(*opt)
        opt = opt.inject(0) { |b, o| b |= o.kind_of?(Integer) ? o : OPTIONS[o] }
        sys_ptrace(COMMAND[:SETOPTIONS], @pid, 0, opt)
end
setregs(buf) click to toggle source
# File metasm/os/linux.rb, line 505
def setregs(buf)
        buf = buf.str if buf.respond_to?(:str)
        sys_ptrace(COMMAND[:SETREGS], @pid, 0, buf)
end
setsiginfo(si=siginfo) click to toggle source
# File metasm/os/linux.rb, line 603
def setsiginfo(si=siginfo)
        si = si.str if si.respond_to?(:str)
        sys_ptrace(COMMAND[:SETSIGINFO], @pid, 0, si)
end
setup_sys_ptrace(sysnr) click to toggle source
# File metasm/os/linux.rb, line 128
        def setup_sys_ptrace(sysnr)
                moo = Class.new(DynLdr)
                case @@host_csn
                when 'ia32'
                        # XXX compat lin2.4 ?
                        asm = <<EOS
#define off 3*4
push ebx
push esi
mov eax, #{sysnr}
mov ebx, [esp+off]
mov ecx, [esp+off+4]
mov edx, [esp+off+8]
mov esi, [esp+off+12]
call gs:[10h]
pop esi
pop ebx
ret
EOS
                when 'x64'
                        asm = <<EOS
#define off 3*8
mov rax, #{sysnr}
//mov rdi, rdi
//mov rsi, rdi
//mov rdx, rdx
mov r10, rcx
syscall
ret
EOS
                else raise 'unsupported target architecture'
                end

                moo.new_func_asm 'long ptrace(unsigned long, unsigned long, unsigned long, unsigned long)', asm
                moo
        end
siginfo() click to toggle source
# File metasm/os/linux.rb, line 591
def siginfo
        @siginfo ||= (
                cp.parse SIGINFO_C if not cp.toplevel.struct['siginfo']
                cp.alloc_c_struct('siginfo')
        )
end
singleblock(sig = nil) click to toggle source
# File metasm/os/linux.rb, line 558
def singleblock(sig = nil)
        sig ||= 0
        sys_ptrace(COMMAND[:SINGLEBLOCK], @pid, 0, sig)
end
singlestep(sig = nil) click to toggle source
# File metasm/os/linux.rb, line 553
def singlestep(sig = nil)
        sig ||= 0
        sys_ptrace(COMMAND[:SINGLESTEP], @pid, 0, sig)
end
str_ptr(str) click to toggle source
# File metasm/os/linux.rb, line 171
def str_ptr(str)
        [str].pack('P').unpack(@packint).first
end
sys_ptrace(req, pid, addr, data) click to toggle source
# File metasm/os/linux.rb, line 459
def sys_ptrace(req, pid, addr, data)
        ret = @sys_ptrace.ptrace(req, pid, addr, data)
        if ret < 0 and ret > -256
                raise SystemCallError.new("ptrace #{COMMAND.index(req) || req}", -ret)
        end
        ret
end
syscall(sig = nil) click to toggle source
# File metasm/os/linux.rb, line 563
def syscall(sig = nil)
        sig ||= 0
        sys_ptrace(COMMAND[:SYSCALL], @pid, 0, sig)
end
traceme() click to toggle source
# File metasm/os/linux.rb, line 467
def traceme
        sys_ptrace(COMMAND[:TRACEME], 0, 0, 0)
end
tweak_for_pid(pid=@pid, tgcpu=nil) click to toggle source

setup variables according to the target (ptrace interface, syscall nrs, …)

# File metasm/os/linux.rb, line 89
def tweak_for_pid(pid=@pid, tgcpu=nil)
        # use these for our syscalls PTRACE
        @@host_csn ||= LinOS.open_process(::Process.pid).cpu.shortname
        case @@host_csn
        when 'ia32'
                @packint = 'l'
                @packuint = 'L'
                @host_intsize = 4
                @host_syscallnr = SYSCALLNR_I386
                @reg_off = REGS_I386
        when 'x64'
                @packint = 'q'
                @packuint = 'Q'
                @host_intsize = 8
                @host_syscallnr = SYSCALLNR_X86_64
                @reg_off = REGS_X86_64
        else raise 'unsupported architecture'
        end

        @tgcpu = tgcpu || LinOS.open_process(pid).cpu
        # use these to interpret the child state
        case @tgcpu.shortname
        when 'ia32'
                @syscallreg = 'ORIG_EAX'
                @syscallnr = SYSCALLNR_I386
                @intsize = 4
        when 'x64'
                @syscallreg = 'ORIG_RAX'
                @syscallnr = SYSCALLNR_X86_64
                @intsize = 8
        else raise 'unsupported target architecture'
        end

        # buffer used in ptrace syscalls
        @buf = [0].pack(@packint)

        @sys_ptrace = @@sys_ptrace[@host_syscallnr['ptrace']] ||= setup_sys_ptrace(@host_syscallnr['ptrace'])
end
wait() click to toggle source
# File metasm/os/linux.rb, line 79
def wait
        ::Process.wait(@pid, ::Process::WALL)
end
writemem(off, str) click to toggle source
# File metasm/os/linux.rb, line 199
def writemem(off, str)
        str.force_encoding('binary') if str.respond_to?(:force_encoding)
        decal = off % @host_intsize
        if decal > 0
                off -= decal
                peekdata(off)
                str = @buf[0...decal] + str
        end
        decal = str.length % @host_intsize
        if decal > 0
                peekdata(off+str.length-decal)
                str += @buf[decal...@host_intsize]
        end
        i = 0
        while i < str.length
                pokedata(off+i, str[i, @host_intsize])
                i += @host_intsize
        end
        true
end