View On Github

RDBG

RDBG is a ruby library that provides binary level debug access to processes.

it wraps the ptrace api to control the target (and read registers), it uses the /proc/$pid/mem and /proc$pid/mapping interfaces to allow proper access to the targets memory and it allows to set breakpoints. Using ptrace to inject breakpoints is actually suprisingly complicated and requires a somewhat complex statemachine to handle events like singlestepping on a breakpoint or capturing signals in combination with breakpoints.

This can be used to automatize various debuggin or reversing task without relaying on the GDB scripting facilites or (even worse) the GDB Machine Interface. For example, we used it to build a cheat engine like tool that allows to find and fixate values in games memory, as demonstrated with the free linux game PARSEC47.

require_relative '../rdbg.rb'

cmd = "/usr/games/parsec47"

# step first ten instructions, printing bytes at EIP and ESP

def get_writable_mappings(d)
  d.mappings.select{|m| m[:permissions].include?("w") && !m[:permissions].include?("s")}
end

def run_and_wait(d)
  # run the debugger until the user enters a value
  d.continue 
  puts "Enter the current value of lives"
  target = gets.strip.to_i
  # pause the debugger
  d.pause
  return target
end

d = RDBG.new(cmd)
target = run_and_wait(d)
offsets =[]
puts "blubl"
get_writable_mappings(d).each do |m|
  puts "reading mapping #{m}"
  str = d.read_mem(m[:range].min, m[:range].max-m[:range].min+1)
  puts str[0..1000].inspect
  str.each_byte.with_index do |byte,offset|
    offsets << m[:range].min+offset if byte == target
  end
  puts "found #{offsets.length} addresses"
end

loop do
  # while no unique locations was identified, loop
  next_target = run_and_wait(d) 
  next_offsets =[]
  offsets.each do |o|
    next_offsets << o if d.read_mem(o,1) == next_target.chr
  end
  offsets = next_offsets
  if offsets.length == 1
    puts "found offset #{offsets.first}"
    break
  end
  puts "found #{offsets.length} addresses"
  puts offsets if offsets.length < 10
end

# we found a pointer to the relevant value, overwrite it with 9999
d.write_mem(offsets.first,[9999].pack("L"))
d.continue

# continue the target application forever
loop do
  sleep 1
end