揭开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功能并不是很一致,因为这里使用了内存加载函数:

  do{

  response =recv(meterpretersock, recvbuf, 1024, 0);

  memcpy(payload,recvbuf,response);

  payload +=response;

  total +=response;

  payloadlength -= response;

  }while(payloadlength > 0);

  payload -=total;

  loadedfile =LoadLibraryR(payload,total);

  meterpreterstart = (MyInit)GetProcAddressR(loadedfile,"Init");

  meterpreterstart(meterpretersock);

  内存加载函数比较复杂,如果将这篇 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代码来实现:

  执行程序,成功得到了meterpreter会话:

  然后我将程序传到 virtualtotal进行检测,发现没有任何杀软报毒:https://www.virustotal.com/en/file/73d54586d85cd54a51befba1c0332c989318bdd228f764d233bad729add4cb33/analysis/1418040884/

  上述模拟 meterpreter stager阶段的cpp代码已托管到 github上:https://github.com/codeliker/mymig_meterpreter

  meterpreter会话建立过程是如此的巧妙。

 

上一篇:智能无惧挑战 山石网科轰动RSA2015

下一篇:“黑客”瞄准某支付公司存有漏洞的早期付费卡 盗刷6万多