top of page

Running Metasploit Shellcode in a Process

Updated: Jul 24, 2023

Recently I was working on a tool similar to Raven that was written by Raphael Mudge, basically that contacts a web server, pulls down some shellcode, and then executes the shellcode. My initial version of the program executed the shellcode by creating a buffer via VirtualAlloc and then passing that buffer (cast as an LPTHREAD_START_ROUTINE) to CreateThread:

     char * sc = VirtualAlloc(NULL,
                    4096,
                   MEM_COMMIT,
                   PAGE_EXECUTE_READWRITE);
 
     // Copy meterpreter reverse HTTPS shellcode to buffer
 
     HANDLE hThread = CreateThread(NULL,
                   0,
                   (LPTHREAD_START_ROUTINE)sc,
                   NULL,
                   0);

The program I was writing was designed to possibly have to run for several weeks, so memory conservation and efficiency were paramount. Using Sysinternals vmmap.exe tool, I began running the tool and monitoring the amount of memory used for the stack, heap, private bytes, working set, etc. What I noticed was puzzling.

Once the meterpreter session is closed and the thread was finished, I would free the memory buffer using VirtualFree and I would close the handle for the thread. However, it seemed that the amount of memory used by the application would increase every time it successfully downloaded and executed the shellcode from the webserver. Once the thread finished, the memory used by that thread was not freed.

Running Metasploit Shellcode in a Process - 1

Figure 1 - Before Shellcode Download / Execute


Running Metasploit Shellcode in a Process - 2

Figure 2 - After Shellcode Download / Execute

Running Metasploit Shellcode in a Process - 3

Figure 3 - Shell Session Opened

Running Metasploit Shellcode in a Process - 4

Figure 4 - Shell Session Killed

Running Metasploit Shellcode in a Process - 5

Figure 5 - Memory used after Meterpreter Session was Killed

Running Metasploit Shellcode in a Process - 6

Figure 6 - After a few more Meterpreter Sessions

To see if it was my application causing the issue, I would try downloading the shellcode, copying it to the buffer, but not executing it via CreateThread. Then I would free the buffer as before. The issue did not persist. Thus it would appear that meterpreter's reverse_https shellcode is allocating buffers and the memory that it allocates is not freed. This makes sense - the first stage contacts the metasploit payload handler and downloads the follow-on stages for meterpreter - allocating memory buffers as needed. The shellcode does not appear to contain code to free the memory that it allocates - this may be a side-effect of optimizing the shellcode for size (extra instructions to free memory require larger shellcode which can be a bad thing). The problem is, this can cause memory leaks in the long-term if the program executes multiple shellcode buffers over the course of its lifetime - the application will reserve more and more memory until it runs out or crashes. This is not ideal for my program since it was supposed to function for medium- to long-term (one-week to multi-week engagements).

Thus I decided that I would need to execute the shellcode in a new process. This should have the effect of freeing any memory used by meterpreter when the process exits. Borrowing the code from inject.c in Raven, I spawn a new process, WriteProcessMem to create an executable buffer in that process, and then CreateRemoteThread on that buffer. This worked fine and there were no more memory management issues within my program. Problem solved - or so I thought.

Using the reverse_https meterpreter, I noticed that even though I had the EXITFUNC set as "process", the process in which the shellcode was executed would not exit when the merterpreter session was terminated. This appears to be related to this bug report for the Metasploit Framework. Apparently the Windows WinInet API is a tad convoluted and doesn't behave as expected when functions are called via the shellcode. When the final shellcode thread exits, for some reason the program does not get the notification and it continues to run merrily along. In the end, this behavior have the effect of leaving a bunch of zombie processes running - obviously not a desirable outcome.

In order for the process to be terminated successfully, it looks like I will have to monitor the thread execution via WaitForSingleObject and then terminate the process once the thread completes:

     // Create the remote thread
     HANDLE hThread = CreateRemoteThread(hProcess,
                    NULL,
                   0,
                   (LPTHREAD_START_ROUTINE)sc,
                   NULL,
                   0,
                   NULL);
 
     // Wait for the thread to terminate
     WaitForSingleObject(hThread, INFINITE);
 
     // Clean Up
     CloseHandle(hThread);
 
     DWORD dwExitCode = 0;
     GetExitCodeProcess(hProcess, &dwExitCode);
 
          if (dwExitCode == STILL_ACTIVE) {
               TerminateProcess(hProcess, 0);
          }

After that, the Process exits cleanly and no more memory leak issues!

 
Polito-Masterful-Cyber-Security

Polito, Inc. offers a wide range of security consulting services including penetration testing, vulnerability assessments, incident response, digital forensics, and more. If your business or your clients have any cyber security needs, contact our experts and experience what Masterful Cyber Security is all about.

Phone: 571-969-7039

Website: politoinc.com

bottom of page