2013年6月6日 星期四

Metasploit将payload生成exe文件的原理探讨

metasploit提供的工具msfpayload和msfencode都支持将payload生成exe可执行文件的格式,但具体的生成机制并没有文档说明,好在这些工具和库文件都是开源的,下面我们来一探究竟。
msfpayload源码中可以看到,参数X支持生成exe格式文件:
(1.rb)download
1
2
3
4
5
6
def usage
$stderr.puts("\n" +
" Usage: #{$0} [<options>] <payload> [var=val] <[S]ummary| C|[P]erl|Rub[y]|[R]aw|[J]s|e[X]e|[D]ll|[V]BA|[W]ar>\n" +
$args.usage)
exit
end


而参数X的处理例程如下:
(2.rb)download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 if (cmd =~ /^x/)
note =
"Created by msfpayload (http://www.metasploit.com).\n" +
"Payload: " + payload.refname + "\n" +
" Length: " + buf.length.to_s + "\n" +
"Options: " + options.inspect + "\n"
arch = payload.arch
plat = payload.platform.platforms
exe = Msf::Util::EXE.to_executable($framework, arch, plat, buf)
if(!exe and plat.index(Msf::Module::Platform::Java))
exe = payload.generate_jar.pack
end
if(exe)
$stderr.puts(note)
$stdout.write(exe)
exit(0)
end
$stderr.puts "No executable format support for this arch/platform"
exit(-1)
end
主要是调用了Msf::Util::EXE库的to_executable方法,我们在这个中找到相应的函数如下:

(3.rb)download
1
2
3
4
5
6
  def self.to_executable(framework, arch, plat, code='', opts={})
if (arch.index(ARCH_X86))
if (plat.index(Msf::Module::Platform::Windows))
return to_win32pe(framework, code, opts)
end
....
最终调用了to_win32pe函数,下面我们来看看这个函数的一些关键地方:

set_template_default(opts, “template_x86_windows.exe”)
这个函数设置了生成exe的模板文件的位置 跟踪这个函数发现模板文件使用的metasploit安装目录的msf3/data/templates/template_x86_windows.exe文件。鉴于生成的exe文件被杀得比较严重,这个模板文件多多少少也脱不了干系,我们可以找个”清白”的exe文件来替换这个模板,注意别改文件名。别外msfencode的-x选项支持自定义模板。
payload = win32_rwx_exec(code)
这个函数利用汇编代码调用win32 api VirtualAlloc申请了一片可读可写可执行的内存块,并将原始payload拷贝到这里,以支持msfencode里调用各种编码器对原始payload进行处理。
然后就是pe格式解析,获得模板文件的PE可执行段(.text),判断其大小,并将其地址保存到mines数组中:
(4.rb)download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 if(not text)
raise RuntimeError, "No .text section found in the template"
end
if ! text.contains_rva?(pe.hdr.opt.AddressOfEntryPoint)
raise RuntimeError, "The .text section does not contain an entry point"
end
if(text.size < (payload.length + 256))
raise RuntimeError, "The .text section is too small to be usable"
end

# Store some useful offsets
off_ent = pe.rva_to_file_offset(pe.hdr.opt.AddressOfEntryPoint)
off_beg = pe.rva_to_file_offset(text.base_rva)

# We need to make sure our injected code doesn't conflict with the
# the data directories stored in .text (import, export, etc)
mines = []
pe.hdr.opt['DataDirectory'].each do |dir|
next if dir.v['Size'] == 0
next if not text.contains_rva?( dir.v['VirtualAddress'] )
mines << [ pe.rva_to_file_offset(dir.v['VirtualAddress']) - off_beg, dir.v['Size'] ]
end
将text段分割成连续的块,将payload保存到最大的块中:

(5.rb)download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 # Break the text segment into contiguous blocks
blocks = []
bidx = 0
mines.sort{|a,b| a[0] <=> b[0]}.each do |mine|
bbeg = bidx
bend = mine[0]
if(bbeg != bend)
blocks << [bidx, bend-bidx]
end
bidx = mine[0] + mine[1]
end
# Add the ending block
if(bidx < text.size - 1)
blocks << [bidx, text.size - bidx]
end
# Find the largest contiguous block
blocks.sort!{|a,b| b[1]<=>a[1]}
block = blocks[0]
# TODO: Allow the entry point in a different block
if(payload.length + 256 > block[1])
raise RuntimeError, "The largest block in .text does not have enough contiguous space (need:#{payload.length+256} found:#{block[1]})"
end
# Make a copy of the entire .text section
data = text.read(0,text.size)
# Pick a random offset to store the payload
poff = rand(block[1] - payload.length - 256)
在其它块中生成大量的随机nops指令,随机生成pe的入口点,但需确保入口点在nops指令块中,在nops块的最后使用跳转指令跳转到payload地址执行。

(6.rb)download
1
2
3
4
5
6
    # Pad the entry point with random nops
entry = generate_nops(framework, [ARCH_X86], rand(200)+51)
...

# Relative jump from the end of the nops to the payload
entry += "\xe9" + [poff - (eidx + entry.length + 5)].pack('V')
最后是pe文件的构造,修改入口点为上面的入口点地址,修改时间戳,检验和等,并保存为exe文件。

归结起来,对模板文件的最大修改是将编码的payload保存到.text段中,生成nops指令和修改入口点,尤其是对入口点的随机修改有明显的人为痕迹,这种exe的生成方式已经被大部分杀软列入黑名单中。 有兴趣的读者可以这样生成不带任务payload的exe文件并上传到virustotal上进行验证:
echo -n | msfencode -e generic/none -t exe > myn.exe [*] generic/none succeeded with size 0 (iteration=1)
明白了原理,免杀就比较简单了,可以另外添加区段或不修改入口点直接将payload从入口点开始覆盖等,发挥你的聪明才智吧。

沒有留言:

張貼留言