Module: Ragweed::Wraposx

Defined in:
lib/ragweed/wraposx.rb,
lib/ragweed/wraposx/wraposx.rb,
lib/ragweed/wraposx/constants.rb,
lib/ragweed/wraposx/region_info.rb,
lib/ragweed/wraposx/region_info.rb,
lib/ragweed/wraposx/thread_info.rb,
lib/ragweed/wraposx/kernelerrorx.rb,
lib/ragweed/wraposx/thread_context.rb,
lib/ragweed/wraposx/thread_context.rb

Defined Under Namespace

Modules: Dl, EFlags, KErrno, KernelReturn, Ptrace, Signal, ThreadInfo, Vm, Wait Classes: KernelCallError, RegionBasicInfo, RegionExtendedInfo, RegionInfo, RegionTopInfo, ThreadContext

Constant Summary collapse

VERSION =

:stopdoc:

'0.1.6'
LIBPATH =
::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
PATH =
::File.dirname(LIBPATH) + ::File::SEPARATOR
LIBS =

These hashes are the magic glue of the ragweed system calls. This one holds the library references from Ruby/DL.

Hash.new do |h, str|
  if not str =~ /^[\.\/].*/
    str = "/usr/lib/" + str
  end
  if not str =~ /.*\.dylib$/
    str = str + ".dylib"
  end
  h[str] = DL.dlopen(str)
end
CALLS =

This hash holds the function references from Ruby/DL. It also auto populates LIBS. CALLS[“<library>!<function>:<argument types>=<return type>”] Hash.new is a beautiful thing.

Hash.new do |h, str|
  lib = proc = args = ret = nil
  lib, rest = str.split "!"
  proc, rest = rest.split ":"
  args, ret = rest.split("=") if rest
  ret ||= "0"
  raise "need proc" if not proc
  h[str] = LIBS[lib][proc, ret + args]
end
NULL =
DL::PtrData.new(0)
SIZEOFINT =
DL.sizeof('I')
SIZEOFLONG =
DL.sizeof('L')
I386_THREAD_STATE_COUNT =

FIXME - constants need to be in separate sub modules XXX - move to class based implementation a la region_info define i386_THREAD_STATE_COUNT ((mach_msg_type_number_t)( sizeof (i386_thread_state_t) / sizeof (int) )) i386_thread_state_t is a struct w/ 16 uint

16
I386_THREAD_STATE =
1
REGISTER_SYMS =
[:eax,:ebx,:ecx,:edx,:edi,:esi,:ebp,:esp,:ss,:eflags,:eip,:cs,:ds,:es,:fs,:gs]

Class Method Summary collapse

Class Method Details

.dl_bignum_to_ulong(x) ⇒ Object

function to marshal 32bit integers into DL::PtrData objects necessary due to Ruby/DL not properly dealing with 31 and 32 bit integers



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/ragweed/wraposx/wraposx.rb', line 167

def dl_bignum_to_ulong(x)
  if x.class == Fixnum
    return DL::PtrData.new(x)
  else
    # shut up
    c = x / 4
    e = x - (c * 4)
    v = DL::PtrData.new 0
    v += c
    v += c
    v += c
    v += c
    v += e
    return v
  end
end

.execv(path, *args) ⇒ Object

Changes execution to file in path with *args as though called from command line.

int execv(const char *path, char *const argv[]);

Raises:

  • (SystemCallError)


342
343
344
345
346
347
348
349
350
# File 'lib/ragweed/wraposx/wraposx.rb', line 342

def execv(path,*args)
  DL.last_error = 0
  argv = ""
  args.flatten.each { |arg| argv = "#{ argv }#{arg.to_ptr.ref.to_s(SIZEOFINT)}" }
  argv += ("\x00"*SIZEOFINT)
  r = CALLS["libc!execv:SP"].call(path,argv.to_ptr).first
  raise SystemCallError.new("execv", DL.last_error) if r == -1
  return r
end

.getpidObject

pid_t getpid(void);

see also getpid(2)



51
52
53
# File 'lib/ragweed/wraposx/wraposx.rb', line 51

def getpid
  CALLS["libc!getpid:=I"].call.first
end

.kill(pid, sig) ⇒ Object

Sends a signal to a process

int kill(pid_t pid, int sig);

See kill(2)

Raises:

  • (SystemCallError)


159
160
161
162
163
# File 'lib/ragweed/wraposx/wraposx.rb', line 159

def kill(pid, sig)
  DL.last_error = 0
  r = CALLS["libc!kill:II=I"].call(pid,sig).first
  raise SystemCallError.new("kill",DL.last_error) if r != 0            
end

.libpath(*args) ⇒ Object

Returns the library path for the module. If any arguments are given, they will be joined to the end of the libray path using File.join.



23
24
25
# File 'lib/ragweed/wraposx.rb', line 23

def self.libpath( *args )
  args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
end

.mach_task_selfObject

From docs at web.mit.edu/darwin/src/modules/xnu/osfmk/man/mach_task_self.html Returns send rights to the task’s kernel port.

mach_port_t mach_task_self(void)

There is no man page for this call.



114
115
116
# File 'lib/ragweed/wraposx/wraposx.rb', line 114

def mach_task_self
  CALLS["libc!mach_task_self:=I"].call().first
end

.path(*args) ⇒ Object

Returns the lpath for the module. If any arguments are given, they will be joined to the end of the path using File.join.



31
32
33
# File 'lib/ragweed/wraposx.rb', line 31

def self.path( *args )
  args.empty? ? PATH : ::File.join(PATH, args.flatten)
end

.ptrace(request, pid, addr, data) ⇒ Object

Apple’s ptrace is fairly gimped. The memory read and write functionality has been removed. We will be using mach kernel calls for that. see vm_read and vm_write. for details on ptrace and the process for the Wraposx/debuggerosx port see: www.matasano.com/log/1100/what-ive-been-doing-on-my-summer-vacation-or-it-has-to-work-otherwise-gdb-wouldnt/

int ptrace(int request, pid_t pid, caddr_t addr, int data);

see also ptrace(2)

Raises:

  • (SystemCallError)


64
65
66
67
68
69
# File 'lib/ragweed/wraposx/wraposx.rb', line 64

def ptrace(request, pid, addr, data)
  DL.last_error = 0
  r = CALLS["libc!ptrace:IIII=I"].call(request, pid, addr, data).first
  raise SystemCallError.new("ptrace", DL.last_error) if r == -1 and DL.last_error != 0
  return r
end

.require_all_libs_relative_to(fname, dir = nil) ⇒ Object

Utility method used to require all files ending in .rb that lie in the directory below this file that has the same name as the filename passed in. Optionally, a specific directory name can be passed in such that the filename does not have to be equivalent to the directory.



40
41
42
43
44
45
46
47
48
# File 'lib/ragweed/wraposx.rb', line 40

def self.require_all_libs_relative_to( fname, dir = nil )
  dir ||= ::File.basename(fname, '.*')
  search_me = ::File.expand_path(
      ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
  
  Dir.glob(search_me).sort.each {|rb| require rb}
  # require File.dirname(File.basename(__FILE__)) + "/#{x}"

end

.sysctl(mib, oldlen = 0, newb = "") ⇒ Object

Used to query kernel state. Returns output buffer on successful call or required buffer size on ENOMEM.

mib: and array of integers decribing the MIB newb: the buffer to replace the old information (only used on some commands so it defaults to empty) oldlenp: output buffer size

int

sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp, size_t newlen);

this function doesn’t really match the Ruby Way(tm)

see sysctl(8)

Raises:

  • (SystemCallError)


297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/ragweed/wraposx/wraposx.rb', line 297

def sysctl(mib,oldlen=0,newb="")
  DL.last_error = 0
  mibp = mib.pack("I_"*mib.size).to_ptr
  oldlenp = [oldlen].pack("I_").to_ptr
  namelen = mib.size
  oldp = (oldlen > 0 ? "\x00"*oldlen : NULL)
  newp = (newb.empty? ? NULL : newb.to_ptr)
  newlen = newb.size
  r = CALLS["libc!sysctl:PIPPPI=I"].call(mibp, namelen, oldp, oldlenp, newp, newlen).first
  return oldlenp.to_str(SIZEOFINT).unpack("I_").first if (r == -1 and DL.last_error == Errno::ENOMEM::Errno)
  raise SystemCallError.new("sysctl", DL.last_error) if r != 0
  return oldp.to_str(oldlenp.to_str(SIZEOFINT).unpack("I_").first)
end

.sysctl_raw(mib, oldlen = 0, newb = "") ⇒ Object

Used to query kernel state. Returns output buffer on successful call and required buffer size as an Array.

mib: and array of integers decribing the MIB newb: the buffer to replace the old information (only used on some commands so it defaults to empty) oldlenp: output buffer size

int

sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp, size_t newlen);

this function doesn’t really match the Ruby Way(tm)

see sysctl(8)

Raises:

  • (SystemCallError)


324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/ragweed/wraposx/wraposx.rb', line 324

def sysctl_raw(mib,oldlen=0,newb="")
  DL.last_error = 0
  mibp = mib.pack('I_'*mib.size).to_ptr
  oldlenp = [oldlen].pack("I_").to_ptr
  namelen = mib.size
  oldp = (oldlen > 0 ? ("\x00"*oldlen).to_ptr : NULL)
  newp = (newb.empty? ? NULL : newb.to_ptr)
  newlen = newb.size
  r = CALLS["libc!sysctl:PIPPPI=I"].call(mibp, namelen, oldp, oldlenp, newp, newlen).first
  ret = (DL.last_error == Errno::ENOMEM::Errno ? NULL : oldp)
  raise SystemCallError.new("sysctl", DL.last_error) if (r != 0 and DL.last_error != Errno::ENOMEM::Errno)
  return [ret,oldlenp.to_str(SIZEOFINT).unpack("I_").first]
end

.task_for_pid(pid, target = nil) ⇒ Object

Requires sudo to use as of 10.5 or 10.4.11(ish) Returns the task id for a process.

kern_return_t task_for_pid(

mach_port_name_t target_tport,
int pid,
mach_port_name_t *t);

There is no man page for this call.

Raises:



127
128
129
130
131
132
133
# File 'lib/ragweed/wraposx/wraposx.rb', line 127

def task_for_pid(pid, target=nil)
  target ||= mach_task_self 
  port = ("\x00"*SIZEOFINT).to_ptr
  r = CALLS["libc!task_for_pid:IIP=I"].call(target, pid, port).first
  raise KernelCallError.new(:task_for_pid, r) if r != 0
  return port.to_s(SIZEOFINT).unpack('i_').first
end

.task_resume(task) ⇒ Object

Resumes a suspended task by id.

kern_return_t task_resume

(task_t         task);

There is no man page for this function.

Raises:



279
280
281
282
# File 'lib/ragweed/wraposx/wraposx.rb', line 279

def task_resume(task)
  r = CALLS["libc!task_resume:I=I"].call(task).first
  raise KernelCallError.new(:task_resume, r) if r != 0            
end

.task_suspend(task) ⇒ Object

Suspends a task by id.

kern_return_t task_suspend

(task_t          task);

There is no man page for this function.

Raises:



268
269
270
271
# File 'lib/ragweed/wraposx/wraposx.rb', line 268

def task_suspend(task)
  r = CALLS["libc!task_suspend:I=I"].call(task).first
  raise KernelCallError.new(:task_suspend, r) if r != 0
end

.task_threads(port) ⇒ Object

Returns an Array of thread IDs for the given task

kern_return_t task_threads

(task_t                                    task,
 thread_act_port_array_t            thread_list,
 mach_msg_type_number_t*           thread_count);

There is no man page for this funtion.

Raises:



143
144
145
146
147
148
149
150
151
# File 'lib/ragweed/wraposx/wraposx.rb', line 143

def task_threads(port)
  threads = ("\x00"*SIZEOFINT).to_ptr
  #threads = 0
  count = ("\x00"*SIZEOFINT).to_ptr
  r = CALLS["libc!task_threads:IPP=I"].call(port, threads, count).first
  t = DL::PtrData.new(threads.to_s(SIZEOFINT).unpack('i_').first)
  raise KernelCallError.new(:task_threads, r) if r != 0
  return t.to_a("I", count.to_s(SIZEOFINT).unpack('I_').first)
end

.thread_get_state(thread) ⇒ Object

Returns a Hash of the thread’s registers given a thread id.

kern_return_t thread_get_state

(thread_act_t                     target_thread,
 thread_state_flavor_t                   flavor,
 thread_state_t                       old_state,
 mach_msg_type_number_t         old_state_count);

Raises:



146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/ragweed/wraposx/thread_context.rb', line 146

def thread_get_state(thread)
  state_arr = ("\x00"*SIZEOFINT*I386_THREAD_STATE_COUNT).to_ptr
  count = ([I386_THREAD_STATE_COUNT].pack("I_")).to_ptr
  r = CALLS["libc!thread_get_state:IIPP=I"].call(thread, I386_THREAD_STATE, state_arr, count).first
  raise KernelCallError.new(:thread_get_state, r) if r != 0
  r = state_arr.to_s(I386_THREAD_STATE_COUNT*SIZEOFINT).unpack("I_"*I386_THREAD_STATE_COUNT)
  regs = Hash.new
  I386_THREAD_STATE_COUNT.times do |i|
    regs[REGISTER_SYMS[i]] = r[i]
  end
  return regs
end

.thread_get_state_raw(thread) ⇒ Object

Returns string representation of a thread’s registers for unpacking given a thread id

kern_return_t thread_get_state

(thread_act_t                     target_thread,
 thread_state_flavor_t                   flavor,
 thread_state_t                       old_state,
 mach_msg_type_number_t         old_state_count);

Raises:



166
167
168
169
170
171
172
# File 'lib/ragweed/wraposx/thread_context.rb', line 166

def thread_get_state_raw(thread)
  state_arr = ("\x00"*SIZEOFINT*I386_THREAD_STATE_COUNT).to_ptr
  count = ([I386_THREAD_STATE_COUNT].pack("I_")).to_ptr
  r = CALLS["libc!thread_get_state:IIPP=I"].call(thread, I386_THREAD_STATE, state_arr, count).first
  raise KernelCallError.new(:thread_get_state, r) if r != 0
  return state_arr.to_s(I386_THREAD_STATE_COUNT*SIZEOFINT)
end

.thread_info_raw(thread, flavor) ⇒ Object

Returns the packed string representation of the thread_info_t struct for later parsing. kern_return_t thread_info

(thread_act_t                     target_thread,
 thread_flavor_t                         flavor,
 thread_info_t                      thread_info,
 mach_msg_type_number_t       thread_info_count);

Raises:



217
218
219
220
221
222
223
# File 'lib/ragweed/wraposx/thread_info.rb', line 217

def thread_info_raw(thread, flavor)
  info = ("\x00"*1024).to_ptr
  count = ([Ragweed::Wraposx::ThreadInfo::FLAVORS[flavor][:count]].pack("I_")).to_ptr
  r = CALLS["libc!thread_info:IIPP=I"].call(thread,flavor,info,Ragweed::Wraposx::ThreadInfo::FLAVORS[flavor][:count]).first
  raise KernelCallError.new(r) if r != 0
  return info.to_s(Ragweed::Wraposx::ThreadInfo::FLAVORS[flavor][:size])
end

.thread_resume(thread) ⇒ Object

Resumes a suspended thread by id.

kern_return_t thread_resume

(thread_act_t                     target_thread);

There is no man page for this function.

Raises:



246
247
248
249
# File 'lib/ragweed/wraposx/wraposx.rb', line 246

def thread_resume(thread)
  r = CALLS["libc!thread_resume:I=I"].call(thread).first
  raise KernelCallError.new(:thread_resume, r) if r != 0
end

.thread_set_state(thread, state) ⇒ Object

Sets the register state of thread from a Hash containing it’s values.

kern_return_t thread_set_state

(thread_act_t                     target_thread,
 thread_state_flavor_t                   flavor,
 thread_state_t                       new_state,
 target_thread                  new_state_count);

Raises:



181
182
183
184
185
186
187
188
189
# File 'lib/ragweed/wraposx/thread_context.rb', line 181

def thread_set_state(thread, state)
  s = Array.new
  I386_THREAD_STATE_COUNT.times do |i|
    s << state[REGISTER_SYMS[i]]
  end
  s = s.pack("I_"*I386_THREAD_STATE_COUNT).to_ptr
  r = CALLS["libc!thread_set_state:IIPI=I"].call(thread, I386_THREAD_STATE, s, I386_THREAD_STATE_COUNT).first
  raise KernelCallError.new(:thread_set_state, r) if r!= 0
end

.thread_set_state_raw(thread, state) ⇒ Object

Sets the register state of thread from a packed string containing it’s values.

kern_return_t thread_set_state

(thread_act_t                     target_thread,
 thread_state_flavor_t                   flavor,
 thread_state_t                       new_state,
 target_thread                  new_state_count);

Raises:



198
199
200
201
# File 'lib/ragweed/wraposx/thread_context.rb', line 198

def thread_set_state_raw(thread, state)
  r = CALLS["libc!thread_set_state:IIPI=I"].call(thread, I386_THREAD_STATE, state.to_ptr, I386_THREAD_STATE_COUNT).first
  raise KernelCallError.new(:thread_set_state, r) if r!= 0
end

.thread_suspend(thread) ⇒ Object

Suspends a thread by id.

kern_return_t thread_suspend

(thread_act_t                     target_thread);

There is no man page for this function.

Raises:



257
258
259
260
# File 'lib/ragweed/wraposx/wraposx.rb', line 257

def thread_suspend(thread)
  r = CALLS["libc!thread_suspend:I=I"].call(thread).first
  raise KernelCallError.new(:thread_suspend, r) if r != 0
end

.timeObject

time_t time(time_t *tloc);

see also time(3)



43
44
45
# File 'lib/ragweed/wraposx/wraposx.rb', line 43

def time
  CALLS["libc!time:=I"].call.first
end

.versionObject

Returns the version string for the library.



15
16
17
# File 'lib/ragweed/wraposx.rb', line 15

def self.version
  VERSION
end

.vm_protect(task, addr, size, setmax, prot) ⇒ Object

Changes the protection state beginning at addr for size bytes to the mask prot. If setmax is true this will set the maximum permissions, otherwise it will set FIXME

kern_return_t vm_protect

(vm_task_t           target_task,
 vm_address_t            address,
 vm_size_t                  size,
 boolean_t           set_maximum,
 vm_prot_t        new_protection);

There is no man page for this function.

Raises:



232
233
234
235
236
237
238
# File 'lib/ragweed/wraposx/wraposx.rb', line 232

def vm_protect(task, addr, size, setmax, prot)
  addr = dl_bignum_to_ulong(addr)
  setmax = setmax ? 1 : 0
  r = CALLS["libc!vm_protect:IPIII=I"].call(task,addr,size,setmax,prot).first
  raise KernelCallError.new(:vm_protect, r) if r != 0
  return nil
end

.vm_read(task, addr, sz = 256) ⇒ Object

Reads sz bytes from task’s address space starting at addr.

kern_return_t vm_read_overwrite

(vm_task_t                           target_task,
 vm_address_t                        address,
 vm_size_t                           size,
 vm_address_t                        *data_out,
 mach_msg_type_number_t              *data_size);

There is no man page for this function.

Raises:



194
195
196
197
198
199
200
201
# File 'lib/ragweed/wraposx/wraposx.rb', line 194

def vm_read(task, addr, sz=256)
  addr = dl_bignum_to_ulong(addr)
  buf = ("\x00" * sz).to_ptr
  len = (sz.to_l32).to_ptr
  r = CALLS["libc!vm_read_overwrite:IPIPP=I"].call(task, addr, sz, buf, len).first
  raise KernelCallError.new(:vm_read, r) if r != 0
  return buf.to_str(len.to_str(4).to_l32)
end

.vm_region_raw(task, address, flavor) ⇒ Object

Returns a string containing the memory region information for task at address. Currently Apple only supports the basic flavor. The other two flavors are included for completeness.

kern_return_t vm_region

(vm_task_t                    target_task,
 vm_address_t                     address,
 vm_size_t                           size,
 vm_region_flavor_t                flavor,
 vm_region_info_t                    info,
 mach_msg_type_number_t        info_count,
 memory_object_name_t         object_name);

Raises:



239
240
241
242
243
244
245
246
247
248
# File 'lib/ragweed/wraposx/region_info.rb', line 239

def vm_region_raw(task, address, flavor)
  info = ("\x00"*64).to_ptr
  count = ([Vm::FLAVORS[flavor][:count]].pack("I_")).to_ptr
  address = ([address].pack("L_")).to_ptr
  objn = ([0].pack("I_")).to_ptr
  sz = ("\x00"*SIZEOFINT).to_ptr
  r = CALLS["libc!vm_region:IPPIPPP=I"].call(task, address, sz, flavor, info, count, objn).first
  raise KernelCallError.new(:vm_region, r) if r != 0
  return "#{info.to_s(Vm::FLAVORS[flavor][:size])}#{address.to_s(SIZEOFLONG)}#{sz.to_s(SIZEOFINT)}"
end

.vm_write(task, addr, val) ⇒ Object

Writes val to task’s memory space at address addr. It is necessary for val.size to report the size of val in bytes

kern_return_t vm_write

(vm_task_t                          target_task,
 vm_address_t                           address,
 pointer_t                                 data,
 mach_msg_type_number_t              data_count);

There is no man page for this function.

Raises:



213
214
215
216
217
218
219
# File 'lib/ragweed/wraposx/wraposx.rb', line 213

def vm_write(task, addr, val)
  addr = dl_bignum_to_ulong(addr)
  val = val.to_ptr
  r = CALLS["libc!vm_write:IPPI=I"].call(task, addr, val, val.size).first
  raise KernelCallError.new(:vm_write, r) if r != 0
  return nil
end

.waitObject

Oringially coded for use in debuggerosx but I’ve switched to waitpid for usability and debugging purposes.

Returns status of child when child recieves a signal.

pid_t wait(int *stat_loc);

see also wait(2)

Raises:

  • (SystemCallError)


80
81
82
83
84
85
# File 'lib/ragweed/wraposx/wraposx.rb', line 80

def wait
  status = ("\x00"*SIZEOFINT).to_ptr
  r = CALLS["libc!wait:=I"].call(status).first
  raise SystemCallError.new("wait", DL.last_error) if r== -1
  return status.to_s(SIZEOFINT).unpack('i_').first
end

.waitpid(pid, opt = 1) ⇒ Object

The wait used in debuggerosx. opt is an OR of the options to be used.

Returns an array. The first element is the pid of the child process as returned by the waitpid system call. The second, the status as an integer of that pid.

pid_t waitpid(pid_t pid, int *stat_loc, int options);

see also wait(2)

Raises:

  • (SystemCallError)


98
99
100
101
102
103
104
105
# File 'lib/ragweed/wraposx/wraposx.rb', line 98

def waitpid(pid, opt=1)
  pstatus = ("\x00"*SIZEOFINT).to_ptr
  r = CALLS["libc!waitpid:IPI=I"].call(pid, pstatus, opt).first
  raise SystemCallError.new("waitpid", DL.last_error) if r== -1
  
  # maybe I should return a Hash?
  return [r, pstatus.to_s(SIZEOFINT).unpack('i_').first]
end