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

142 lines
4.3 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
# Default list of supported GDB features to send the to the target
GDB_FEATURES = 'qSupported:multiprocess+;qRelocInsn+;qvCont+;'
# Maps arch -> index of register in GDB that holds $PC
PC_REGISTERS = {
ARCH_X86 => '08',
ARCH_X64 => '10',
ARCH_X86_64 => '10'
}
# Send an ACK packet
def send_ack
sock.put('+')
vprint_status('Sending ack...')
end
# Reads an ACK packet from the wire
# @raise ['received bad ack'] if a bad ACK is received
def read_ack
unless sock.get_once == '+'
raise 'received bad ack'
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
def read_response(opts={})
decode = opts.fetch(:decode, false)
res = sock.get_once
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
# Gets the current instruction by stepping and parsing the PC register from response
# @return [String] containing the hex-encoded address stored in EIP
def get_pc
# on x64 it is the register under the key "10"
idx = pc_reg_index(payload.arch)
pc = step.split(';').map { |r| r =~ /#{idx}:([a-f0-9]*)/ and $1 }.compact.first
# convert to desired endian/ptr size for a given arch
addr = Rex::Arch.pack_addr(payload.arch, Integer(pc, 16))
Rex::Text.to_hex(addr, '')
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
# 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
# Executes one instruction on the remote process
# @return [String] a list of key/value pairs, including current PC
def step
send_cmd 'vCont;s'
read_response(decode: true)
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
# @param my_arch [String, Array] the current system architecture
# @return [String] hex index of the register that contains $PC for the current arch
def pc_reg_index(my_arch)
if my_arch.is_a?(Array) then my_arch = my_arch[0] end
PC_REGISTERS[my_arch]
end
end
end