揭开Meterpreter的神秘面纱

写在前面

用过 metasploit 的人应该对 meterpreter 不陌生,它具有强大的功能,特别是其socks 代理,简直就是内网渗透测试神器。由于 meterpreter 功能强大,萌生了想要把msf 做远控的念头。另外,围绕 meterpreter 一个重要的问题,就是如何绕过杀软。

这促使我决定一窥 meterpreter 的究竟,以了解其工作原理和流程,从而方便自定义载荷进行免杀,或者予以改造做远控。 后来发现,直接基于 msf 做远控还有很长一段路要走,不过却可以完美免杀,而且还了解了 meterpreter 会话建立的流程和原理,对写程序很受启发。

剖析meterpreter

使用 msf 生成的 meterpreter 的 shellcode 很小 (stager 阶段 ) ,如 287 bytes ,一个 287bytes 的 shellcode 竟能最终建立一个功能强大的 meterpreter 会话。这段shellcode 到底是在干什么呢?这段 shellcode 就只干了如下事情:连接服务器,接收载荷,并将控制权转移到载荷。

有人利用 C 语言实现了 meterpreter 建立会话(这里可以参考链接: ‍ ‍http://diablohorn.wordpress.com/2013/02/04/evade-antivirus-convert-shellcode-to-c ) meterpreter 会话的建立过程,但是和原 shellcode 功能并不是很一致,因为这里使用了内存加载函数:

 

内存加载函数比较复杂,如果将这篇 c 代码转为shellcode,我想是不可能只有287bytes的。于是,我去掉了其内存加载的地方,直接将控制权转移到载荷,并将socket作为参数传入,发现会话并没有正常建立,但是也没有报错。

因此,我决定对原始shellcode进行分析,以找到meterpreter会话建立的核心流程。

在分析 原始 shellcode 之前,我想先知道被控端连接 msf 后, msf 发过来的是什么载荷。通过查阅资料,得知这个载荷名叫: metsvr.xxx.dll , xxx 代表 x86 或者 x64。为了验证,我删掉了/opt/metasploit/apps/pro/vendor/bundle/ruby/1.9.1/gems/meterpreter_bins-0.0.11/meterpreter 目录下的 metsrv.x86.dll ,发现会话无法正常建立了,从而确定了就是这个 dll 。然后我重新编译了 metsrv.x86.dll ,并插入了添加的代码,发现添加的代码执行了,进一步说明定位准确。

不过这样,问题就来了。

通常,我们自己写程序执行 meterpreter 的 shellcode ,常常是执行将控制权转移到shellcode 执行,而上面那篇 c 代码,却是先加载载荷到内存中,然后再调用其 Init() 函数。显然两者差距很大,但是都能正常建立其 meterpreter 会话。也就是说, msf 传过来的载荷,既可以直接将控制权转移到载荷执行,也可以内存加载载荷,然后调用其 init 函数来执行。是什么原因使得这两种方式都可以的呢?刚开始,我以为难道可以直接将控制权转移到 dll 头部,就会调用其主函数?显然,这种想法一经测试,立刻知道是错误的了。那么,原因就只有一个,一定是 Msf 对 dll 做了手脚。

为了验证我的想法,我修改了上面提到的 c 代码,当接收到载荷后,将其保存在文件中。并与原始 dll 进行了对比:

结果显示,的确不同。从第 3 个字节开始,就出现了变化。因此,我对这段代码进行了反汇编分析:

发现这部分是经过精心构造的代码,由于前两个字节是 4D 5A, 对应的反汇编代码是DEC BEP,POP EDX 。这段改编后的代码先存储了当前地址在 EBX 中,然后恢复因为4D 5A 而造成的数据的更改。然后调用了偏移 15E7 处的函数,接着执行了该函数返回结果对应的函数。步入 15E7 分析:

这里像是在向回找到 4D 5A ,也就是 DLL 的头部。继续往下面分析,发现非常像是找在函数地址,这里会不会就是内存加载 DLL 的地方呢?如果是这样,内存加载 DLL 后,应该返回 DLL 的 DllMain 函数的地址,后来发现果然如此。最后,我查阅资料终于找到了插入到 4D 5A 后面的代码原始说明:

这段代码验证了我的猜测。(注释已经说的很清楚的,大家可以自己分析下)

联想到起初我提到的那篇 c 代码,因为我不想要那段内存加载的地方(因为太大了,转为 shellcode 会导致 shellcode 太过庞大),而去掉了内存加载的地方,直接执行载荷,但是会话却没有成功建立。看到这里的说明,原因很清楚了,因为我是通过参数传递将 socket 的值传过去的,而这里插入的代码,是通过寄存器 edi 得到 socket 的值,也就是说我们应该将 socket 存放到 edi 中,然后在将控制权转移到载荷中去。这样就可以用很简短的 c 代码,来模拟 meterpreter stager ,并成功建立会话。

为了验证,我编写了 cpp 代码来实现: