2014-08-24 06:10:30 +00:00
|
|
|
|
# -*- 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-08-24 06:10:30 +00:00
|
|
|
|
# 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'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 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(target.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(target.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 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(arch)
|
|
|
|
|
if arch.is_a?(Array)
|
|
|
|
|
arch = arch[0]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
PC_REGISTERS[arch]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
end
|