# -*- 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