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

191 lines
5.8 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# -*- 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
include Msf::Exploit::Remote::Tcp
# thrown when an expected ACK packet is never received
class BadAckError < RuntimeError; end
# thrown when a response is incorrect
class BadResponseError < RuntimeError; end
# thrown when a checksum is invalid
class BadChecksumError < 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
# @raise [BadAckError] if a bad ACK is received
def read_ack
unless sock.get_once(1) == '+'
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
# @option opts :verify [Boolean] verify the response's checksum
# @return [String] the response
# @raise [BadResponseError] if the expected response is missing
# @raise [BadChecksumError] if the checksum is invalid
def read_response(opts={})
decode, verify = opts.fetch(:decode, false), opts.fetch(:verify, true)
res = sock.get_once
raise BadResponseError if res.nil?
raise BadChecksumError if (verify && !verify_checksum(res))
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 str [String] 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
# Verifies a response's checksum
# @param res [String] the response to check
# @return [Boolean] whether the checksum is valid
def verify_checksum(res)
msg, chksum = res.match(/^\$(.*)#(\h{2})$/)[1..2]
checksum(msg) == chksum
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
# @raise [BadResponseError] if necessary data is missing
def process_info
data = step
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,
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
# 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
def run(filename)
send_cmd "vRun;#{Rex::Text.to_hex(filename, '')}"
read_response
end
def enable_extended_mode
send_cmd("!")
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_cmd features
read_response # lots of flags, nothing interesting
end
end
end