metasploit-framework/lib/msf/core/exploit/gdb.rb

173 lines
5.2 KiB
Ruby
Raw Normal View History

# -*- coding: binary -*-
require 'msf/core/exploit/tcp'
module Msf
#
# Implement some helpers for communicating with a remote gdb instance.
#
# More info on the gdb protocol can be found here:
# https://sourceware.org/gdb/current/onlinedocs/gdb/Overview.html#Overview
#
module Exploit::Remote::Gdb
2014-08-24 06:17:32 +00:00
include Msf::Exploit::Remote::Tcp
2014-09-03 20:56:21 +00:00
# thrown when an expected ACK packet is never received
class BadAckError < RuntimeError; end
# thrown when a response is incorrect
class BadResponseError < RuntimeError; end
# Default list of supported GDB features to send the to the target
GDB_FEATURES = 'qSupported:multiprocess+;qRelocInsn+;qvCont+;'
# Maps index of register in GDB that holds $PC to architecture
PC_REGISTERS = {
'08' => ARCH_X86,
'10' => ARCH_X86_64
}
# Send an ACK packet
def send_ack
sock.put('+')
vprint_status('Sending ack...')
end
# Reads an ACK packet from the wire
2014-09-03 20:56:21 +00:00
# @raise [BadAckError] if a bad ACK is received
def read_ack
unless sock.get_once == '+'
2014-09-03 20:56:21 +00:00
raise BadAckError
end
vprint_status('Received ack...')
end
# Sends a command and receives an ACK from the remote.
# @param cmd [String] the gdb command to run. The command is will be
# wrapped '$' prefix and checksum.
def send_cmd(cmd)
full_cmd = '$' + cmd + '#' + checksum(cmd)
vprint_status('Sending cmd: '+full_cmd)
sock.put(full_cmd)
read_ack
end
# Reads (and possibly decodes) from the socket and sends an ACK to verify receipt
# @param opts [Hash] the options hash
# @option opts :decode [Boolean] rle decoding should be applied to the response
# @return [String] the response
2014-09-03 20:56:21 +00:00
# @raise [BadResponseError] if the expected response is missing
def read_response(opts={})
decode = opts.fetch(:decode, false)
res = sock.get_once
2014-09-03 20:56:21 +00:00
raise BadResponseError if res.nil?
res = decode_rle(res) if decode
vprint_status('Result: '+res)
send_ack
res
end
# Implements decoding of gdbserver's Run-Length-Encoding that is applied
# on some hex values to collapse repeated characters.
#
# https://sourceware.org/gdb/current/onlinedocs/gdb/Overview.html#Binary-Data
#
# @param msg [String] the message to decode
# @return [String] the decoded result
def decode_rle(msg)
vprint_status "Before decoding: #{msg}"
msg.gsub /.\*./ do |match|
match.bytes.to_a.first.chr * (match.bytes.to_a.last - 29 + 1)
end
end
# The two-digit checksum is computed as the modulo 256 sum of all characters
# between the leading $ and the trailing # (an eight bit unsigned checksum).
# @param [String] str the string to calculate the checksum of
# @return [String] hex string containing checksum
def checksum(str)
"%02x" % str.bytes.inject(0) { |b, sum| (sum+b)%256 }
end
# Writes the buffer +buf+ to the address +addr+ in the remote process's memory
# @param buf [String] the buffer to write
# @param addr [String] the hex-encoded address to write to
def write(buf, addr)
hex = Rex::Text.to_hex(buf, '')
send_cmd "M#{addr},#{buf.length.to_s(16)}:#{hex}"
read_response
end
# Steps execution and finds $PC pointer and architecture
# @return [Hash] with :arch and :pc keys containing architecture and PC pointer
2014-09-03 20:56:21 +00:00
# @raise [BadResponseError] if necessary data is missing
def process_info
data = step
2014-09-03 20:56:21 +00:00
pc_data = data.split(';')[2]
raise BadResponseError if pc_data.nil?
pc_data = pc_data.split(':')
my_arch = PC_REGISTERS[pc_data[0]]
pc = pc_data[1]
if my_arch.nil?
raise RuntimeError, "Could not detect a supported arch from response to step:\n#{pc_data}"
end
{
arch: my_arch,
2014-09-03 20:56:21 +00:00
pc: Rex::Text.to_hex(Rex::Arch.pack_addr(my_arch, Integer(pc, 16)), ''),
pc_raw: Integer(pc, 16)
}
end
# Continues execution of the remote process
# @param opts [Hash] the options hash
# @option opts :read [Boolean] read the response
def continue(opts={})
send_cmd 'vCont;c'
read_response if opts.fetch(:read, true)
end
2014-09-03 19:56:22 +00:00
# Detaches from the remote process
# @param opts [Hash] the options hash
# @option opts :read [Boolean] read the response
def detach(opts={})
send_cmd 'D'
read_response if opts.fetch(:read, true)
end
# Executes one instruction on the remote process
#
# The results of running "step" will look like:
# x86: $T0505:00000000;04:a0f7ffbf;08:d2f0fdb7;thread:p2d39.2d39;core:0;#53
# x64: $T0506:0000000000000000;07:b0587f9fff7f0000;10:d3e29d03057f0000;thread:p8bf9.8bf9;core:0;#df
# The third comma-separated field will contain EIP, and the register index
# will let us deduce the remote architecture (through PC_REGISTERS lookup)
#
# @return [String] a list of key/value pairs, including current PC
def step
send_cmd 'vCont;s'
read_response(decode: true)
end
2014-09-03 20:56:21 +00:00
def run(filename)
send_cmd "vRun;#{Rex::Text.to_hex(filename, '')}"
read_response
end
# Performs a handshake packet exchange
# @param features [String] the list of supported features to tell the remote
# host that the client supports (defaults to +DEFAULT_GDB_FEATURES+)
def handshake(features=GDB_FEATURES)
send_ack
send_cmd features
read_response # lots of flags, nothing interesting
end
end
end