# -*- 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 # 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 == '+' 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 # @raise [BadResponseError] if the expected response is missing def read_response(opts={}) decode = opts.fetch(:decode, false) res = sock.get_once 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 # @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_ack send_cmd features read_response # lots of flags, nothing interesting end end end