Skip to content

Add ARM64 cross-compilation support for Windows Meterpreter#794

Open
SilentSobs wants to merge 7 commits into
rapid7:masterfrom
SilentSobs:feature/windows-arm64-metsrv
Open

Add ARM64 cross-compilation support for Windows Meterpreter#794
SilentSobs wants to merge 7 commits into
rapid7:masterfrom
SilentSobs:feature/windows-arm64-metsrv

Conversation

@SilentSobs

@SilentSobs SilentSobs commented Mar 15, 2026

Copy link
Copy Markdown

Add MSVC ARM64 platform support to metsrv

This PR adds ARM64 Windows support to metsrv the core Meterpreter server DLL. It builds on the groundwork already merged by @xaitax (ARM64 ReflectiveLoader in rapid7/ReflectiveDLLInjection#20 and WinExec payload in rapid7/metasploit-framework#20357) and extends it to the full metsrv DLL.

The goal was simple: get metsrv.ARM64.dll building cleanly with MSVC (Visual Studio 2022, v143 toolset) targeting ARM64 Windows. Took a few iterations to get there ARM64 is stricter than x86/x64 in a few ways that exposed some assumptions baked into the existing codebase.


What changed and why

metsrv.vcxproj

Added Debug|ARM64, Release|ARM64, and r7_release|ARM64 configurations. ARM64 requires the modern v143 toolset the existing v141_xp used by other platforms doesn't support it. Assembly is wired through MARMASM (Microsoft ARM Assembler) using marmasm.props/marmasm.targets not MASM, which is x86 only. x86/x64 trampolines are excluded from ARM64 builds and vice versa. RandomizedBaseAddress is forced to true for ARM64 configs since /DYNAMICBASE:NO is explicitly rejected by the ARM64 linker.

metsrv.def

Removed the NAME server.dll directive. This was causing a silent linker failure the ARM64 output filename is metsrv.ARM64.dll and the mismatched NAME directive was preventing the DLL from being produced at all.

base_inject.c / base_inject.h / metapi.c

Guarded inject_via_remotethread_wow64 with #ifndef _M_ARM64 in both the definition and the declaration. The WOW64 x86→x64 transition mechanism simply doesn't exist on ARM64 there's no 32-bit subsystem to bridge to. Also guarded the function pointer reference in the inject API table in metapi.c the same way.

pool_party_ext.h

Changed the _CLIENT_ID struct guard from #ifndef __MINGW32__ to #if !defined(__MINGW32__) && !defined(_WINTERNL_). The newer Windows SDK (10.0.26100.0) already defines _CLIENT_ID in winternl.h, so the local definition was causing a redefinition error.

common.h

Added #include <ws2tcpip.h> after #include <winsock2.h>. Without it, inet_pton is simply not declared.

metsrv.h

The file was doing #undef _WIN32_WINNT then hardcoding _WIN32_WINNT_WINXP, which overrode any fix made in common.h. Added a conditional so ARM64 gets 0x0600 (Vista) instead ARM64 Windows minimum is 10, but Vista is the minimum needed to get inet_pton and other modern Winsock APIs without errors.

server_transport_tcp.c

Replaced inet_addr with inet_pton and gethostbyname with getaddrinfo. Also replaced WSADuplicateSocketA with WSADuplicateSocketW and updated the WSAPROTOCOL_INFOA field in TCPMIGRATECONTEXT to WSAPROTOCOL_INFOW. These deprecated ANSI Winsock APIs are hard errors on ARM64 with the newer SDK.

server_pivot_named_pipe.c

Renamed the local AddMandatoryAce function to AddMandatoryAce_ to avoid a conflict with the SDK's dllimport declaration. At _WIN32_WINNT=0x0600 the Windows SDK exposes AddMandatoryAce as a dllimport, which conflicts with the existing local static definition.


Build output

Tested on Windows 10 x64 with Visual Studio 2022 (v143 toolset, SDK 10.0.26100.0):

msbuild metsrv.vcxproj /p:Configuration=Debug /p:Platform=ARM64 /p:VCToolsVersion=14.44.35207
image

Notes

  • Only metsrv.vcxproj was modified other projects in the solution (elevator, stdapi etc.) have pre-existing v141_xp issues that are unrelated to this PR
  • Extensions (stdapi, priv) and the Ruby payload module are follow-on work this PR focuses on getting the core DLL building first
  • MinGW cross-compilation support (Linux → ARM64 Windows) was explored in an earlier iteration but MSVC was prioritized per @dledda-r7's feedback

- Add aarch64-w64-mingw32.cmake toolchain file for llvm-mingw
- Add ARM64 build targets to Makefile (meterpreter-arm64, meterpreter-metsrv-arm64)
- Add aarch64 architecture detection in workspace CMakeLists.txt
- Add ARM64 source selection in ReflectiveDLLInjection CMakeLists.txt

Tested with llvm-mingw 20260311, produces valid PE32+ ARM64 DLL.
@jbx81-1337

Copy link
Copy Markdown
Contributor

Helllo @SilentSobs, Thank you for opening this PR.

We do support Mingw, but our main compiler (for example what we ship in metasploit-framework) is MSVC, so the first step should be extend the VisualStudio projects to use ARM64 along side what you already did for Mingw.

Secondly, Porting Meterpreter to ARM64 not only requires the compiler additions, but mainly includes working on the code-base and looking for wrong architecture assumptions.

something like:

if(dwMeterpreterArch == ARCH_X64) {
// something x64
else {
// something x32 
}

I would expect a significant effort ensuring in framework we don't have this assumption also. I made last year a small issue
rapid7/metasploit-framework#20385 to describe that could be considered an MVP

Cheers!

Add Release, Debug, and r7_release configurations for ARM64 target.
Uses v143 toolset (VS2022) as ARM64 requires modern toolchain.
Wires GateTrampolineARM64.asm under MARMASM (Microsoft ARM Assembler),
excludes x86/x64 trampolines from ARM64 builds and vice versa.

fix: use ARMASM64 instead of MASM for GateTrampolineARM64.asm

fix: use MARMASM instead of ARMASM for GateTrampolineARM64.asm
Several parts of the metsrv codebase made implicit assumptions about
x86/x64 that break on ARM64. This commit addresses all of them:

- base_inject.c/h: guard inject_via_remotethread_wow64 with
  #ifndef _M_ARM64 — WOW64 x86->x64 transition doesn't exist on ARM64

- metapi.c: guard inject_via_remotethread_wow64 function pointer in
  the inject API table with #ifndef _M_ARM64

- pool_party_ext.h: change _CLIENT_ID guard to also check _WINTERNL_
  since newer Windows SDK (10.0.26100.0) already defines it there

- common.h: add ws2tcpip.h include to make inet_pton available

- metsrv.h: conditionally set _WIN32_WINNT to 0x0600 for ARM64
  since ARM64 Windows minimum is Vista, not XP

- server_transport_tcp.c: replace deprecated inet_addr/gethostbyname
  with inet_pton/getaddrinfo, replace WSADuplicateSocketA with W variant

- server_pivot_named_pipe.c: rename local AddMandatoryAce to
  AddMandatoryAce_ to avoid conflict with SDK dllimport declaration

- metsrv.vcxproj: set RandomizedBaseAddress=true for ARM64 configs
  since /DYNAMICBASE:NO is not allowed on ARM64

- metsrv.def: remove NAME directive that conflicted with ARM64 output
  filename causing linker failure
@SilentSobs SilentSobs force-pushed the feature/windows-arm64-metsrv branch from 8e8ffe8 to b5ad9f0 Compare March 20, 2026 08:24
Comment thread c/meterpreter/source/common/common.h Outdated
Comment on lines +17 to +22
// Minimum Windows Vista for inet_pton and modern APIs
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600
#endif
#include <winsock2.h>
#include <ws2tcpip.h>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change should be wrapped inside an #ifdef _M_ARM64 otherwise we are breaking compatibility with Windows XP

* threads (kernel32!CreateThread will return ERROR_NOT_ENOUGH_MEMORY). Because of this we filter out
* Windows 2003 from this method of injection, however the APC injection method will work on 2003.
*/
#ifndef _M_ARM64

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should remove the function on ARM, just throwing an incompatibility error if the architecture is not X86 or X64

Comment on lines +472 to +482
if (dwMeterpreterArch == PROCESS_ARCH_X86 && dwDestinationArch == PROCESS_ARCH_X64)
{
BREAK_ON_ERROR("[INJECT] inject_via_remotethread: migrate_via_remotethread_wow64 failed")
dwTechnique = MIGRATE_TECHNIQUE_REMOTETHREADWOW64;

if (inject_via_remotethread_wow64(hProcess, lpStartAddress, lpParameter, &hThread) != ERROR_SUCCESS)
{
BREAK_ON_ERROR("[INJECT] inject_via_remotethread: migrate_via_remotethread_wow64 failed")
}
}
}
else
else
#endif // _M_ARM64

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also incorrect. the handling should be by verifying dwMeterpreterArch and dwDestinationArch rather than using ifdefs

Comment thread c/meterpreter/source/metsrv/metapi.c Outdated
inject_dll,
inject_via_apcthread,
inject_via_remotethread,
#ifndef _M_ARM64

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one shouldn't be removed

Comment thread c/meterpreter/source/metsrv/metsrv.h Outdated
Comment on lines +14 to +18
#ifdef _M_ARM64
#define _WIN32_WINNT 0x0600
#else
#define _WIN32_WINNT _WIN32_WINNT_WINXP
#endif

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as up here

Comment on lines +42 to +43
typedef BOOL (WINAPI *PAddMandatoryAce_)(PACL pAcl, DWORD dwAceRevision, DWORD dwAceFlags, DWORD dwMandatoryPolicy, PSID pLabelSid);
static BOOL WINAPI AddMandatoryAce_(PACL pAcl, DWORD dwAceRevision, DWORD dwAceFlags, DWORD dwMandatoryPolicy, PSID pLabelSid)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for this change?

{
COMMONMIGRATECONTEXT common;
WSAPROTOCOL_INFOA info;
WSAPROTOCOL_INFOW info;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swapping this could harm the XP release builds

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to necro-comment, but per some recent discussions, dropping XP support may be in scope?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello! From 6.5 we are effectively dropping XP support, I believe this PR will be handled AFTER 6.5 so that should not be a problem anymore

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants