|
| 1 | +# -*- coding: binary -*- |
| 2 | + |
| 3 | +require 'msf/core/exploit/tcp' |
| 4 | + |
| 5 | +module Msf |
| 6 | + |
| 7 | +# |
| 8 | +# Implement some helpers for communicating with a remote gdb instance. |
| 9 | +# |
| 10 | +# More info on the gdb protocol can be found here: |
| 11 | +# https://sourceware.org/gdb/current/onlinedocs/gdb/Overview.html#Overview |
| 12 | +# |
| 13 | + |
| 14 | +module Exploit::Remote::Gdb |
| 15 | + |
| 16 | + include Msf::Exploit::Remote::Tcp |
| 17 | + |
| 18 | + # thrown when an expected ACK packet is never received |
| 19 | + class BadAckError < RuntimeError; end |
| 20 | + |
| 21 | + # thrown when a response is incorrect |
| 22 | + class BadResponseError < RuntimeError; end |
| 23 | + |
| 24 | + # thrown when a checksum is invalid |
| 25 | + class BadChecksumError < RuntimeError; end |
| 26 | + |
| 27 | + # Default list of supported GDB features to send the to the target |
| 28 | + GDB_FEATURES = 'qSupported:multiprocess+;qRelocInsn+;qvCont+;' |
| 29 | + |
| 30 | + # Maps index of register in GDB that holds $PC to architecture |
| 31 | + PC_REGISTERS = { |
| 32 | + '08' => ARCH_X86, |
| 33 | + '10' => ARCH_X86_64 |
| 34 | + } |
| 35 | + |
| 36 | + # Send an ACK packet |
| 37 | + def send_ack |
| 38 | + sock.put('+') |
| 39 | + vprint_status('Sending ack...') |
| 40 | + end |
| 41 | + |
| 42 | + # Reads an ACK packet from the wire |
| 43 | + # @raise [BadAckError] if a bad ACK is received |
| 44 | + def read_ack |
| 45 | + unless sock.get_once(1) == '+' |
| 46 | + raise BadAckError |
| 47 | + end |
| 48 | + vprint_status('Received ack...') |
| 49 | + end |
| 50 | + |
| 51 | + # Sends a command and receives an ACK from the remote. |
| 52 | + # @param cmd [String] the gdb command to run. The command is will be |
| 53 | + # wrapped '$' prefix and checksum. |
| 54 | + def send_cmd(cmd) |
| 55 | + full_cmd = '$' + cmd + '#' + checksum(cmd) |
| 56 | + vprint_status('Sending cmd: '+full_cmd) |
| 57 | + sock.put(full_cmd) |
| 58 | + read_ack |
| 59 | + end |
| 60 | + |
| 61 | + # Reads (and possibly decodes) from the socket and sends an ACK to verify receipt |
| 62 | + # @param opts [Hash] the options hash |
| 63 | + # @option opts :decode [Boolean] rle decoding should be applied to the response |
| 64 | + # @option opts :verify [Boolean] verify the response's checksum |
| 65 | + # @return [String] the response |
| 66 | + # @raise [BadResponseError] if the expected response is missing |
| 67 | + # @raise [BadChecksumError] if the checksum is invalid |
| 68 | + def read_response(opts={}) |
| 69 | + decode, verify = opts.fetch(:decode, false), opts.fetch(:verify, true) |
| 70 | + res = sock.get_once |
| 71 | + raise BadResponseError if res.nil? |
| 72 | + raise BadChecksumError if (verify && !verify_checksum(res)) |
| 73 | + res = decode_rle(res) if decode |
| 74 | + vprint_status('Result: '+res) |
| 75 | + send_ack |
| 76 | + res |
| 77 | + end |
| 78 | + |
| 79 | + # Implements decoding of gdbserver's Run-Length-Encoding that is applied |
| 80 | + # on some hex values to collapse repeated characters. |
| 81 | + # |
| 82 | + # https://sourceware.org/gdb/current/onlinedocs/gdb/Overview.html#Binary-Data |
| 83 | + # |
| 84 | + # @param msg [String] the message to decode |
| 85 | + # @return [String] the decoded result |
| 86 | + def decode_rle(msg) |
| 87 | + vprint_status "Before decoding: #{msg}" |
| 88 | + msg.gsub /.\*./ do |match| |
| 89 | + match.bytes.to_a.first.chr * (match.bytes.to_a.last - 29 + 1) |
| 90 | + end |
| 91 | + end |
| 92 | + |
| 93 | + # The two-digit checksum is computed as the modulo 256 sum of all characters |
| 94 | + # between the leading ‘$’ and the trailing ‘#’ (an eight bit unsigned checksum). |
| 95 | + # @param str [String] the string to calculate the checksum of |
| 96 | + # @return [String] hex string containing checksum |
| 97 | + def checksum(str) |
| 98 | + "%02x" % str.bytes.inject(0) { |b, sum| (sum+b)%256 } |
| 99 | + end |
| 100 | + |
| 101 | + # Verifies a response's checksum |
| 102 | + # @param res [String] the response to check |
| 103 | + # @return [Boolean] whether the checksum is valid |
| 104 | + def verify_checksum(res) |
| 105 | + msg, chksum = res.match(/^\$(.*)#(\h{2})$/)[1..2] |
| 106 | + checksum(msg) == chksum |
| 107 | + end |
| 108 | + |
| 109 | + # Writes the buffer +buf+ to the address +addr+ in the remote process's memory |
| 110 | + # @param buf [String] the buffer to write |
| 111 | + # @param addr [String] the hex-encoded address to write to |
| 112 | + def write(buf, addr) |
| 113 | + hex = Rex::Text.to_hex(buf, '') |
| 114 | + send_cmd "M#{addr},#{buf.length.to_s(16)}:#{hex}" |
| 115 | + read_response |
| 116 | + end |
| 117 | + |
| 118 | + # Steps execution and finds $PC pointer and architecture |
| 119 | + # @return [Hash] with :arch and :pc keys containing architecture and PC pointer |
| 120 | + # @raise [BadResponseError] if necessary data is missing |
| 121 | + def process_info |
| 122 | + data = step |
| 123 | + pc_data = data.split(';')[2] |
| 124 | + raise BadResponseError if pc_data.nil? |
| 125 | + pc_data = pc_data.split(':') |
| 126 | + my_arch = PC_REGISTERS[pc_data[0]] |
| 127 | + pc = pc_data[1] |
| 128 | + |
| 129 | + if my_arch.nil? |
| 130 | + raise RuntimeError, "Could not detect a supported arch from response to step:\n#{pc_data}" |
| 131 | + end |
| 132 | + |
| 133 | + { |
| 134 | + arch: my_arch, |
| 135 | + pc: Rex::Text.to_hex(Rex::Arch.pack_addr(my_arch, Integer(pc, 16)), ''), |
| 136 | + pc_raw: Integer(pc, 16) |
| 137 | + } |
| 138 | + end |
| 139 | + |
| 140 | + # Continues execution of the remote process |
| 141 | + # @param opts [Hash] the options hash |
| 142 | + # @option opts :read [Boolean] read the response |
| 143 | + def continue(opts={}) |
| 144 | + send_cmd 'vCont;c' |
| 145 | + read_response if opts.fetch(:read, true) |
| 146 | + end |
| 147 | + |
| 148 | + # Detaches from the remote process |
| 149 | + # @param opts [Hash] the options hash |
| 150 | + # @option opts :read [Boolean] read the response |
| 151 | + def detach(opts={}) |
| 152 | + send_cmd 'D' |
| 153 | + read_response if opts.fetch(:read, true) |
| 154 | + end |
| 155 | + |
| 156 | + # Executes one instruction on the remote process |
| 157 | + # |
| 158 | + # The results of running "step" will look like: |
| 159 | + # x86: $T0505:00000000;04:a0f7ffbf;08:d2f0fdb7;thread:p2d39.2d39;core:0;#53 |
| 160 | + # x64: $T0506:0000000000000000;07:b0587f9fff7f0000;10:d3e29d03057f0000;thread:p8bf9.8bf9;core:0;#df |
| 161 | + # The third comma-separated field will contain EIP, and the register index |
| 162 | + # will let us deduce the remote architecture (through PC_REGISTERS lookup) |
| 163 | + # |
| 164 | + # @return [String] a list of key/value pairs, including current PC |
| 165 | + def step |
| 166 | + send_cmd 'vCont;s' |
| 167 | + read_response(decode: true) |
| 168 | + end |
| 169 | + |
| 170 | + def run(filename) |
| 171 | + send_cmd "vRun;#{Rex::Text.to_hex(filename, '')}" |
| 172 | + read_response |
| 173 | + end |
| 174 | + |
| 175 | + def enable_extended_mode |
| 176 | + send_cmd("!") |
| 177 | + read_response |
| 178 | + end |
| 179 | + |
| 180 | + # Performs a handshake packet exchange |
| 181 | + # @param features [String] the list of supported features to tell the remote |
| 182 | + # host that the client supports (defaults to +DEFAULT_GDB_FEATURES+) |
| 183 | + def handshake(features=GDB_FEATURES) |
| 184 | + send_cmd features |
| 185 | + read_response # lots of flags, nothing interesting |
| 186 | + end |
| 187 | + |
| 188 | +end |
| 189 | + |
| 190 | +end |
0 commit comments