Skip to content

Commit 6eedf6a

Browse files
committed
Land rapid7#4975, egghunter tool, Resolve rapid7#4944
2 parents 6353154 + 71c544c commit 6eedf6a

File tree

2 files changed

+259
-0
lines changed

2 files changed

+259
-0
lines changed

spec/tools/egghunter_spec.rb

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
load Metasploit::Framework.root.join('tools/egghunter.rb').to_path
2+
3+
require 'stringio'
4+
5+
describe Egghunter do
6+
7+
describe Egghunter::Driver do
8+
9+
subject do
10+
Egghunter::Driver.new
11+
end
12+
13+
let(:egg) {
14+
'W00T'
15+
}
16+
17+
describe '#run' do
18+
19+
def get_stdout(&block)
20+
out = $stdout
21+
$stdout = fake = StringIO.new
22+
begin
23+
yield
24+
ensure
25+
$stdout = out
26+
end
27+
fake.string
28+
end
29+
30+
let(:default_opts) {
31+
{ :platform => 'windows', :format => 'c', :eggtag => egg, :arch => 'x86' }
32+
}
33+
34+
before(:each) do
35+
allow(Egghunter::OptsConsole).to receive(:parse).with(any_args).and_return(options)
36+
end
37+
38+
context 'when the platform is windows' do
39+
let(:options) { default_opts }
40+
41+
it 'returns a windows egghunter' do
42+
output = get_stdout { subject.run }
43+
expect(output).to include("\\x66\\x81\\xca\\xff")
44+
end
45+
end
46+
47+
context 'when the platform is linux' do
48+
let(:options) do
49+
{ :platform => 'linux', :format => 'c', :eggtag => egg, :arch => 'x86' }
50+
end
51+
52+
it 'returns a linux egghunter' do
53+
output = get_stdout { subject.run }
54+
expect(output).to include("\\xfc\\x66\\x81\\xc9\\xff")
55+
end
56+
end
57+
58+
context 'when the egg is WOOT' do
59+
let(:options) { default_opts }
60+
61+
it 'includes W00T in the egghunter' do
62+
output = get_stdout { subject.run }
63+
expect(output).to include("\\x57\\x30\\x30\\x54")
64+
end
65+
end
66+
end
67+
end
68+
69+
70+
describe Egghunter::OptsConsole do
71+
subject do
72+
Egghunter::OptsConsole
73+
end
74+
75+
context 'when no options are given' do
76+
it 'raises OptionParser::MissingArgument' do
77+
expect{subject.parse([])}.to raise_error(OptionParser::MissingArgument)
78+
end
79+
end
80+
81+
context 'when no format is specified and --list-formats isn\'t used' do
82+
it 'raises OptionParser::MissingArgument' do
83+
args = '-e AAAA'.split
84+
expect{subject.parse(args)}.to raise_error(OptionParser::MissingArgument)
85+
end
86+
end
87+
88+
context 'when no egg is specified and --list-formats isn\'t used' do
89+
it 'raises OptionParser::MissingArgument' do
90+
args = '-f python'.split
91+
expect{subject.parse(args)}.to raise_error(OptionParser::MissingArgument)
92+
end
93+
end
94+
95+
context 'when :depsize is a string' do
96+
it 'raises OptionParser::InvalidOption' do
97+
args = '-e AAAA -f c --depsize STRING'.split
98+
expect{subject.parse(args)}.to raise_error(OptionParser::InvalidOption)
99+
end
100+
end
101+
end
102+
103+
end

tools/egghunter.rb

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#!/usr/bin/env ruby
2+
3+
msfbase = __FILE__
4+
while File.symlink?(msfbase)
5+
msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
6+
end
7+
$:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib')))
8+
require 'msfenv'
9+
require 'rex'
10+
require 'msf/core'
11+
require 'msf/base'
12+
require 'optparse'
13+
14+
module Egghunter
15+
class OptsConsole
16+
def self.parse(args)
17+
options = {}
18+
parser = OptionParser.new do |opt|
19+
opt.banner = "Usage: #{__FILE__} [options]\nExample: #{__FILE__} -f python -e W00T"
20+
opt.separator ''
21+
opt.separator 'Specific options:'
22+
23+
opt.on('-f', '--format <String>', "See --list-formats for a list of supported output formats") do |v|
24+
options[:format] = v
25+
end
26+
27+
opt.on('-b', '--badchars <String>', "(Optional) Bad characters to avoid for the egg") do |v|
28+
options[:badchars] = v
29+
end
30+
31+
opt.on('-e', '--egg <String>', "The egg (Please give 4 bytes)") do |v|
32+
options[:eggtag] = v
33+
end
34+
35+
opt.on('-p', '--platform <String>', "(Optional) Platform") do |v|
36+
options[:platform] = v
37+
end
38+
39+
opt.on('--startreg <String>', "(Optional) The starting register") do |v|
40+
# Do not change this key. This should matching the one in Rex::Exploitation::Egghunter
41+
options[:startreg] = v
42+
end
43+
44+
opt.on('--forward', "(Optional) To search forward") do |v|
45+
# Do not change this key. This should matching the one in Rex::Exploitation::Egghunter
46+
options[:searchforward] = true
47+
end
48+
49+
opt.on('--depreg <String>', "(Optional) The DEP register") do |v|
50+
# Do not change this key. This should matching the one in Rex::Exploitation::Egghunter
51+
options[:depreg] = v
52+
end
53+
54+
opt.on('--depdest <String>', "(Optional) The DEP destination") do |v|
55+
# Do not change this key. This should matching the one in Rex::Exploitation::Egghunter
56+
options[:depdest] = v
57+
end
58+
59+
opt.on('--depsize <Fixnum>', "(Optional) The DEP size") do |v|
60+
# Do not change this key. This should matching the one in Rex::Exploitation::Egghunter
61+
options[:depsize] = v
62+
end
63+
64+
opt.on('--depmethod <String>', "(Optional) The DEP method to use (virtualprotect/virtualalloc/copy/copy_size)") do |v|
65+
# Do not change this key. This should matching the one in Rex::Exploitation::Egghunter
66+
options[:depmethod] = v
67+
end
68+
69+
opt.on('-a', '--arch <String>', "(Optional) Architecture") do |v|
70+
# Although this is an option, this is currently useless because we don't have x64 egghunters
71+
options[:arch] = v
72+
end
73+
74+
opt.on('--list-formats', "List all supported output formats") do
75+
options[:list_formats] = true
76+
end
77+
78+
opt.on('-v', '--var-name <name>', String, '(Optional) Specify a custom variable name to use for certain output formats') do |v|
79+
options[:var_name] = v
80+
end
81+
82+
opt.on_tail('-h', '--help', 'Show this message') do
83+
$stdout.puts opt
84+
exit
85+
end
86+
end
87+
88+
parser.parse!(args)
89+
90+
if options.empty?
91+
raise OptionParser::MissingArgument, 'No options set, try -h for usage'
92+
elsif options[:format].blank? && !options[:list_formats]
93+
raise OptionParser::MissingArgument, '-f is required'
94+
elsif options[:eggtag].blank? && !options[:list_formats]
95+
raise OptionParser::MissingArgument, '-e is required'
96+
elsif options[:format] && !::Msf::Simple::Buffer.transform_formats.include?(options[:format])
97+
raise OptionParser::InvalidOption, "#{options[:format]} is not a valid format"
98+
elsif options[:depsize] && options[:depsize] !~ /^\d+$/
99+
raise OptionParser::InvalidOption, "--depsize must be a Fixnum"
100+
end
101+
102+
options[:badchars] = '' unless options[:badchars]
103+
options[:platform] = 'windows' unless options[:platform]
104+
options[:arch] = ARCH_X86 unless options[:arch]
105+
options[:var_name] = 'buf' unless options[:var_name]
106+
107+
options
108+
end
109+
end
110+
111+
class Driver
112+
def initialize
113+
begin
114+
@opts = OptsConsole.parse(ARGV)
115+
rescue OptionParser::ParseError => e
116+
$stderr.puts "[x] #{e.message}"
117+
exit
118+
end
119+
end
120+
121+
def run
122+
# list_formats should check first
123+
if @opts[:list_formats]
124+
list_formats
125+
return
126+
end
127+
128+
egghunter = Rex::Exploitation::Egghunter.new(@opts[:platform], @opts[:arch])
129+
raw_code = egghunter.hunter_stub('', @opts[:badchars], @opts)
130+
output_stream = $stdout
131+
output_stream.binmode
132+
output_stream.write ::Msf::Simple::Buffer.transform(raw_code, @opts[:format], @opts[:var_name])
133+
$stderr.puts
134+
end
135+
136+
private
137+
138+
def list_formats
139+
$stderr.puts "[*] Supported output formats:"
140+
$stderr.puts ::Msf::Simple::Buffer.transform_formats.join(", ")
141+
end
142+
143+
end
144+
end
145+
146+
147+
if __FILE__ == $PROGRAM_NAME
148+
driver = Egghunter::Driver.new
149+
begin
150+
driver.run
151+
rescue ::Exception => e
152+
elog("#{e.class}: #{e.message}\n#{e.backtrace * "\n"}")
153+
$stderr.puts "[x] #{e.class}: #{e.message}"
154+
$stderr.puts "[*] If necessary, please refer to framework.log for more details."
155+
end
156+
end

0 commit comments

Comments
 (0)