Meterpreter 0 to 1

本文通过分析 metasploit 源代码中的 payload 部分:

官方文档

对于像 metasploit 这样代码量比较大的项目来说,官方文档和项目中附带的各种 README.md 是最有价值的参考信息,关于 payload,值得关注的部分:

How payloads work

modules/payloads/{singles,stages,stagers}/<platform>

  • Staged payloads: <platform>/[arch]/<stage>/<stager>

  • Single payloads: <platform>/[arch]/<single>

Singles

Single payloads are fire-and-forget. They can create a communications mechanism with Metasploit, but they don't have to. An example of a scenario where you might want a single is when the target has no network access -- a fileformat exploit delivered via USB key is still possible.

Stagers

Stagers are a small stub designed to create some form of communication and then pass execution to the next stage. Using a stager solves two problems. First, it allows us to use a small payload initially to load up a larger payload with more functionality. Second, it makes it possible to separate the communications mechanism from the final stage so one payload can be used with multiple transports without duplicating code.

Stages

Since the stager will have taken care of dealing with any size restrictions by allocating a big chunk of memory for us to run in, stages can be arbitrarily large. One advantage of that is the ability to write final-stage payloads in a higher-level language like C.

Meterpreter Stageless Mode

PayloadStagedStageless
Reverse TCPwindows/meterpreter/reverse_tcpwindows/meterpreter_reverse_tcp
Reverse HTTPSwindows/meterpreter/reverse_httpswindows/meterpreter_reverse_https
Bind TCPwindows/meterpreter/bind_tcpwindows/meterpreter_bind_tcp
Reverse TCP IPv6windows/meterpreter/reverse_ipv6_tcpwindows/meterpreter_reverse_ipv6_tcp

In order to get a Meterpreter session running in the example scenario we uploaded the following:

  • stage0: large buffer of junk plus approximately 350b of shellcode.

  • stage1: 32bit metsrv DLL approximately 169kb plus the configuration block.

  • stage2: 32bit stdapi DLL approximately 332kb.

  • stage3: priv DLL approximately 104kb.

What does stageless Meterpreter look like?

As with the staged version, stageless Meterpreter payloads begin with a small bootstrapper. However, this bootstrapper looks very different. Staged Meterpreter payload bootstrappers contain shellcode that performs network communications in order to read in the second stage prior to invoking it. The stageless counterparts don't have this responsibility, as it is instead handled by metsrv itself. As a result, what we know as stage0 completely disappears.

Instead, that which is known as stage1 in staged Meterpreter land becomes the bootstrapper for the payload in stageless Meterpreter land. To make this clear, let's take a look at the process.

When creating the payload, Metasploit first reads a copy of the metsrv DLL into memory. It then overwrites the DLL's DOS header with a selection of shellcode that does the following:

  1. Performs a simple GetPC routine.

  2. Calculates the location of the ReflectiveLoader() function in metsrv.

  3. Invokes the ReflectiveLoader() function in metsrv.

  4. Calculates the location of the start of a custom Configuration Block that contains information about transports, extensions and extension-specific initialisation scripts. This configuration block appears in memory immediately after metsrv.

  5. Invokes DllMain() on metsrv, passing in DLL_METASPLOIT_ATTACH along with the pointer to the configuration block. This is where metsrv takes over.

With this shellcode stub wired into the DOS header, Metasploit adds the entire binary blob to an in-memory payload buffer and then iterates through the list of chosen extensions. For each extension that is specified, Metasploit does the following:

  1. Loads the extension DLL into memory.

  2. Calculates the size of the DLL.

  3. Writes the size of the DLL as a 32-bit value to the configuration block.

  4. Writes the entire body of the DLL, as-is, to the end of the conifiguration block.

Once the end of the list of extensions is reached, the last thing that is written to the payload buffer is a 32-bit representation of 0 (NULL) which indicates that the list of extensions has been terminated. This NULL value is what metsrv will look for when iterating through the list of extensions so that it knows when to stop. After this, any extension initialisation scripts are wired in (though that's beyond the scope of this article).

The final payload layout looks like the following:

This payload can be embedded in an exe file, encoded, thrown into an exploit (assuming there's room!), and who knows what else! The important thing is that we now have all of the bits that we need in the one payload.

Meterpreter Configuration

Stage 1 of loading Windows Meterpreter now utilises a new loader, called meterpreter_loader (Win x86, Win x64), which does the following:

  • Loads the metsrv DLL from disk.

  • Patches the DOS header of the DLL so that it contains executable shellcode that correctly initializes metsrv and calculates the location that points to the end of metsrv in memory. It also takes any existing socket value (found in edi or rdi depending on the architecture) and writes that directly to the configuration (more on this later).

  • Generates a configuration block and appends this to the metsrv binary.

The result is that the payload has the following structure once it has been prepared:

Configuration block overview

To summarise, the following shows the layout of a full configuration:

build instructions

源码分析

了解完以上内容,基本上对 metasploit payload 能有一个总体的了解,接下来跟进源码部分:

stageless

代码位置:metasploit\modules\payloads

windows/meterpreter_reverse_tcp

metasploit\modules\payloads\stages\windows\meterpreter.rb

转到 metasploit\lib\msf\core\payload\windows\meterpreter_loader.rb,这个文件是生成 metsrv DLL 的核心代码:

由 stage_payload 方法生成的是原始 payload,-o exe 生成的文件会有额外的处理。stage_meterpreter 是一个反射 DLL,但是可以和 shellcode 使用相同的方式加载执行。

原理在于将一段汇编引导程序写入 DOS 头:

上面这段代码前两条指令没有实际作用,主要是字节码为 MZ 4d5a,在后面将这两个寄存器的值恢复了。

64 位

64 位会把 4d5a 解析为 pop r10,所以开始要附加一条 push r10 还原,pop-call 获取内存位置,调用 loader 函数,offset 计算后硬编码,使用 loader 函数的返回结果调用 dllmain。

ReflectiveLoader.c

返回值:

这里有一个问题,为什么要保留 MZ,直接执行引导头不行吗?

由于 payload 是自映射的,需要使用 DOS 头的地方就是 ReflectiveLoader。在 ReflectiveLoader.c 搜索 DOS_HEADER,会发现只需要两个信息 e_magic "MZ" 来判断是否是 PE 和 e_lfanew 定位 NT 头。

DOS_HEADER 结构体:

除了第一项和最后一项都是无用信息,可以随意修改。这种处理很巧妙,使 DOS头 既是 PE 文件格式的一部分,同时又是可执行代码。

分析 load_rdi_dll(MetasploitPayloads.meterpreter_path('metsrv', 'x86.dll')),在 metasploit\lib\msf\core\reflective_dll_loader.rb 中,代码逻辑很简单,解析 PE 然后返回 dll 和 offset,不太清楚的是 meterpreter_path 是哪个路径。

由于事先读过 Windows Native C meterpreter 构建文档:

Once you've made changes and compiled a new .dll or .so, copy the contents of the output/ directory into your Metasploit Framework's data/meterpreter/ directory.

If you made any changes to metsrv.dll ensure that all extensions still load and function properly.

先入为主的认为 meterpreter_path 应该也是这个路径,但是 data 文件夹下只有这三个文件:metsvc-server.exe、metsvc.exe、x64_osx_stage。然后在 metasploit 文件夹下搜索 * metsrv * .dll,没有结果。那么这些文件应该是不包含在源码中的,应该是安装后才有的依赖库文件。

转到安装了 metasploit 的系统中,metasploit 文件夹搜索:

安装后的 metasploit 多一个文件夹 vendor,包含各种依赖库文件。

转到 metasploit-framework/vendor/bundle/ruby/2.7.0/gems/metasploit-payloads-1.4.4/ 文件夹。

目录结构:

查看 metasploit-payloads.rb:

Msf::Config.data_directory :metasploit\lib\msf\base\config.rb

那么文档中为什么会有 data/meterpreter/ 的说法呢?

这里涉及到的代码有点杂,总结来说就是上面那条注释,data 文件夹的优先级大于 gem 文件夹,如果对 metsrv 进行修改和二次开发,就不需要去动 gem 里的文件,便于多次测试且容易复原。

Try the MSF path first to see if the file exists, allowing the MSF data

folder to override what is in the gem. This is very helpful for

testing/development without having to move the binaries to the gem folder

each time. We only do this is MSF is installed.

staged

参考上面的文档,staged payload 会进行分阶段加载,第一阶段是一段汇编 stager,获取并将执行转到 mersrv。

以 windows/meterpreter/reverse_tcp 为例:metasploit\modules\payloads\stagers\windows\x64\reverse_tcp.rb 依赖 metasploit\lib\msf\core\payload\windows\reverse_tcp.rb。

其中每个 api 调用都使用了 GetProcAddressWithHash 这种技术,在 sRDI 的文章中已有分析。

流程可以大致总结为:

meterpreter_loader.rb:

edi 在 stage 的引导头会用到,将这个数据写入 DLL 末尾,也就是配置块的开始。

看一下配置块结构,开始刚好是 Socket Handle 的位置:

to_exe

前两项中所提到的都是 shellcode 形式的 payload,是 msfvenom -l formats 中的 Framework Transform Formats,生成 Framework Executable Formats 还需要对其进行包装,代码在 metasploit\lib\msf\util\exe.rb。

这里代码量很大,就不贴出来了,概括一下就是把 shellcode 转化为 exe,VirtualAlloc 分配内存,ExitProcess 退出进程(查看导入表就这两个函数的),读取 exe 模板,操作区段,修改入口点等,stage exe 其实就是把 metsrv 这个 DLL 整个嵌入到 exe 中作为 shellcode 执行。

这里不分析过多细节是因为 to_exe 的功能模式单一实用性不强,替代它的方法有很多且易于实现。

改造思路

由于 msf 是开源软件,payload 的特征和技术特点肯定是被特殊关照过的了,因此想要有效的规避针对 msf 的安全措施,需要做一些自定义的工作。

通过阅读源码了解了相关技术原理后,修改思路就逐渐清晰了,我把它分为三部分:loader,stager,meterpreter。

loader

这部分就是我在 to_exe 中说的实现较为容易的部分,msfvenom 生成 shellcode,实现一个自定义加载器。

大部分 msf 免杀的入门级文章基本都是在做这个事情,使用不同的内存分配方式、代码执行方式,shellcode 混淆编码加密等。

stager

在分析过了 stager 后,可以不依赖 msfvenom,自己实现 stager 的功能。

只要符合:4 + DLL 的数据传输方式,socket to edi,可以使用任意编程语言任何执行方式来实现其他部分。

与 loader 不同,自定义 stager 可以控制的内容更多,修改 loader 可以改变代码加载执行的方式和静态特征,但是 stager 还可以修改执行代码的内容。

cobaltstrike 作者的 C 实现:

https://blog.cobaltstrike.com/2012/09/13/a-loader-for-metasploits-meterpreter/

https://github.com/rsmudge/metasploit-loader

以这个项目为基础,可以改进的地方有很多:

在对 loader/stager 进行修改后,基本上能稳定的绕过安全软件获得一个 meterpreter shell。

如果想更进一步的话就比较硬核了,需要对 metasploit-payloads 这个代码库进行二次开发,还是比较复杂的。

meterpreter

如果安全软件通过内存特征匹配杀掉了 meterpreter 或者使用 migrate 进程迁移失败被杀掉,或者有一些其他的动态查杀手段,可能就需要修改 meterpreter 来进行规避了。

我的 msf 版本:

不能直接按照文档中的命令 clone,通常最新版都是测试版,要 clone 匹配的版本,不同版本的协议、文件等是不兼容的。

查看 metasploit-framework/vendor/bundle/ruby/2.7.0/gems/metasploit-payloads-1.4.4 得到版本 1.4.4。

现在源码就准备好了。

安装 VS 组件:Desktop Development with C++、C++ Windows XP Support for VS 2017 (v141) tools [Deprecated]。

使用 VS 2019 构建,开发者命令行切换目录到 metasploit-payloads\c\meterpreter。

像文档中一样直接 make 是不行的,使用 cmake 那个脚本才可以构建成功。

现在就有了自己编译的 metsrv.dll、ext_server_stdapi.dll、ext_server_priv.dll 这几个核心 dll。

复制到 data 目录:

生成 payload:

这两条告警好像无论版本是否对应都会出现。

不兼容的版本会多一条错误找不到入口点,这个就是使用最新版源码编译的,但是这个是 msf6 的文件,不兼容。

这条错误的原因虽然我没有去仔细分析,但是大概能猜到原因,msf6 的更新中有一条是修改了定位 loader 函数位置的方式,应该是改成了用序号,所以 5 才找不到。

配置:

可以看到新的 dll 已经在使用了。

执行 a.exe:

迁移到 powershell:

migrate

两个 powershell 进程,可以看到被注入的进程中的 RWX 内存,执行一些其他的命令也没有问题。

至此算是打通了整个 获取源码-编译构建-测试 的整个流程,有了这些基础就可以对 metepreter 进行二次开发了。

以我比较了解的进程注入和反射加载部分为例:

metasploit-payloads\c\meterpreter\source\metsrv\base_inject.h

添加其他进程注入方式:process hollowing,线程劫持,File Mapping 注入等。

分配内存大量使用 RWX:可以从两方面改进,修改保护属性 RW —> RX 而不是直接 RWX,改进调用 VirtualAlloc 的方式(运行时动态链接/或者系统调用),或者使用其他方式如 heap 内存进行内存分配。

loader 执行执行后清除引导头。

loader 的内存保护属性问题: