Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Port AMSI scanning for assembly loads #23231

Merged
merged 3 commits into from Mar 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 16 additions & 14 deletions configurecompiler.cmake
Expand Up @@ -221,11 +221,11 @@ if (WIN32)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG /PDBCOMPRESS")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /STACK:1572864")

# Temporarily disable incremental link due to incremental linking CFG bug crashing crossgen.
# Temporarily disable incremental link due to incremental linking CFG bug crashing crossgen.
# See https://github.com/dotnet/coreclr/issues/12592
# This has been fixed in VS 2017 Update 5 but we're keeping this around until everyone is off
# the versions that have the bug. The bug manifests itself as a bad crash.
set(NO_INCREMENTAL_LINKER_FLAGS "/INCREMENTAL:NO")
set(NO_INCREMENTAL_LINKER_FLAGS "/INCREMENTAL:NO")

# Debug build specific flags
set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "/NOVCFEATURE ${NO_INCREMENTAL_LINKER_FLAGS}")
Expand Down Expand Up @@ -396,6 +396,8 @@ if (CLR_CMAKE_PLATFORM_UNIX)
endif(CLR_CMAKE_PLATFORM_UNIX)

if (WIN32)
add_definitions(-DPLATFORM_WINDOWS=1)

# Define the CRT lib references that link into Desktop imports
set(STATIC_MT_CRT_LIB "libcmt$<$<OR:$<CONFIG:Debug>,$<CONFIG:Checked>>:d>.lib")
set(STATIC_MT_VCRT_LIB "libvcruntime$<$<OR:$<CONFIG:Debug>,$<CONFIG:Checked>>:d>.lib")
Expand Down Expand Up @@ -469,7 +471,7 @@ if (CLR_CMAKE_PLATFORM_UNIX)
# and so the compiler thinks that there is a mistake.
add_compile_options(-Wno-constant-logical-operand)
# We use pshpack1/2/4/8.h and poppack.h headers to set and restore packing. However
# clang 6.0 complains when the packing change lifetime is not contained within
# clang 6.0 complains when the packing change lifetime is not contained within
# a header file.
add_compile_options(-Wno-pragma-pack)

Expand Down Expand Up @@ -559,22 +561,22 @@ if (WIN32)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /guard:cf")
set(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} /guard:cf")

# Statically linked CRT (libcmt[d].lib, libvcruntime[d].lib and libucrt[d].lib) by default. This is done to avoid
# linking in VCRUNTIME140.DLL for a simplified xcopy experience by reducing the dependency on VC REDIST.
#
# For Release builds, we shall dynamically link into uCRT [ucrtbase.dll] (which is pushed down as a Windows Update on downlevel OS) but
# wont do the same for debug/checked builds since ucrtbased.dll is not redistributable and Debug/Checked builds are not
# production-time scenarios.
add_compile_options($<$<OR:$<CONFIG:Release>,$<CONFIG:Relwithdebinfo>>:/MT>)
add_compile_options($<$<OR:$<CONFIG:Debug>,$<CONFIG:Checked>>:/MTd>)
# Statically linked CRT (libcmt[d].lib, libvcruntime[d].lib and libucrt[d].lib) by default. This is done to avoid
# linking in VCRUNTIME140.DLL for a simplified xcopy experience by reducing the dependency on VC REDIST.
#
# For Release builds, we shall dynamically link into uCRT [ucrtbase.dll] (which is pushed down as a Windows Update on downlevel OS) but
# wont do the same for debug/checked builds since ucrtbased.dll is not redistributable and Debug/Checked builds are not
# production-time scenarios.
add_compile_options($<$<OR:$<CONFIG:Release>,$<CONFIG:Relwithdebinfo>>:/MT>)
add_compile_options($<$<OR:$<CONFIG:Debug>,$<CONFIG:Checked>>:/MTd>)

set(CMAKE_ASM_MASM_FLAGS "${CMAKE_ASM_MASM_FLAGS} /ZH:SHA_256")

if (CLR_CMAKE_TARGET_ARCH_ARM OR CLR_CMAKE_TARGET_ARCH_ARM64)
# Contracts work too slow on ARM/ARM64 DEBUG/CHECKED.
add_definitions(-DDISABLE_CONTRACTS)
endif (CLR_CMAKE_TARGET_ARCH_ARM OR CLR_CMAKE_TARGET_ARCH_ARM64)
endif (CLR_CMAKE_TARGET_ARCH_ARM OR CLR_CMAKE_TARGET_ARCH_ARM64)

endif (WIN32)

if(CLR_CMAKE_ENABLE_CODE_COVERAGE)
Expand Down
2 changes: 2 additions & 0 deletions src/vm/CMakeLists.txt
Expand Up @@ -553,12 +553,14 @@ endif(FEATURE_STANDALONE_GC)
if(WIN32)

set(VM_SOURCES_DAC_AND_WKS_WIN32
amsi.cpp
clrtocomcall.cpp
rcwwalker.cpp
winrttypenameconverter.cpp
)

set(VM_HEADERS_DAC_AND_WKS_WIN32
amsi.h
clrtocomcall.h
rcwwalker.h
winrttypenameconverter.h
Expand Down
120 changes: 120 additions & 0 deletions src/vm/amsi.cpp
@@ -0,0 +1,120 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
// File: amsi.cpp
//

#include "common.h"
#include "amsi.h"

namespace
{
// https://docs.microsoft.com/en-us/windows/desktop/api/amsi/
DECLARE_HANDLE(HAMSICONTEXT);
DECLARE_HANDLE(HAMSISESSION);

enum AMSI_RESULT
{
AMSI_RESULT_CLEAN = 0,
AMSI_RESULT_NOT_DETECTED = 1,
AMSI_RESULT_BLOCKED_BY_ADMIN_START = 0x4000,
AMSI_RESULT_BLOCKED_BY_ADMIN_END = 0x4fff,
AMSI_RESULT_DETECTED = 0x8000
} AMSI_RESULT;

bool AmsiResultIsMalware(DWORD result)
{
return result >= AMSI_RESULT_DETECTED;
}

bool AmsiResultIsBlockedByAdmin(DWORD result)
{
return result >= AMSI_RESULT_BLOCKED_BY_ADMIN_START
&& result <= AMSI_RESULT_BLOCKED_BY_ADMIN_END;
}

using PAMSI_AMSISCANBUFFER_API = HRESULT(WINAPI *)(
_In_ HAMSICONTEXT amsiContext,
_In_ PVOID buffer,
_In_ ULONG length,
_In_ LPCWSTR contentName,
_In_opt_ HAMSISESSION session,
_Out_ DWORD *result);

using PAMSI_AMSIINITIALIZE_API = HRESULT(WINAPI *)(
_In_ LPCWSTR appName,
_Out_ HAMSICONTEXT *amsiContext);

PAMSI_AMSISCANBUFFER_API AmsiScanBuffer;
HAMSICONTEXT s_amsiContext;
CRITSEC_COOKIE s_csAmsi;

bool InitializeLock()
{
if (s_csAmsi != nullptr)
return true;

CRITSEC_COOKIE lock = ClrCreateCriticalSection(CrstLeafLock, CRST_REENTRANCY);
if (lock == nullptr)
return false;

if (InterlockedCompareExchangeT<CRITSEC_COOKIE>(&s_csAmsi, lock, nullptr) != nullptr)
ClrDeleteCriticalSection(lock);

return true;
}
}

// Here we will invoke into AmsiScanBuffer, a centralized area for non-OS
// programs to report into Defender (and potentially other anti-malware tools).
// This should only run on in memory loads, Assembly.Load(byte[]) for example.
// Loads from disk are already instrumented by Defender, so calling AmsiScanBuffer
// wouldn't do anything.
bool Amsi::IsBlockedByAmsiScan(PVOID flatImageBytes, COUNT_T size)
{
STANDARD_VM_CONTRACT;

if (!InitializeLock())
return false;

// Lazily initialize AMSI because it is very expensive
{
CRITSEC_Holder csh(s_csAmsi);

// Cache that we failed if this didn't work so we don't keep trying to reinitialize
static bool amsiInitializationAttempted = false;
if (s_amsiContext == nullptr && !amsiInitializationAttempted)
{
HMODULE amsi = CLRLoadLibraryEx(W("amsi.dll"), nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
if (amsi != nullptr)
{
PAMSI_AMSIINITIALIZE_API AmsiInitialize = (PAMSI_AMSIINITIALIZE_API)GetProcAddress(amsi, "AmsiInitialize");
if (AmsiInitialize != nullptr)
{
HAMSICONTEXT amsiContext = nullptr;
if (AmsiInitialize(W("coreclr"), &amsiContext) == S_OK)
{
AmsiScanBuffer = (PAMSI_AMSISCANBUFFER_API)GetProcAddress(amsi, "AmsiScanBuffer");
if (AmsiScanBuffer != nullptr)
{
s_amsiContext = amsiContext;
}
}
}
}

amsiInitializationAttempted = true;
}
}

if (s_amsiContext == nullptr || AmsiScanBuffer == nullptr)
return false;

DWORD result;
HRESULT hr = AmsiScanBuffer(s_amsiContext, flatImageBytes, size, nullptr, nullptr, &result);
elinor-fung marked this conversation as resolved.
Show resolved Hide resolved
if (hr == S_OK && (AmsiResultIsMalware(result) || AmsiResultIsBlockedByAdmin(result)))
return true;

return false;
}
16 changes: 16 additions & 0 deletions src/vm/amsi.h
@@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
// File: amsi.h
//

#ifndef __AMSI_H__
#define __AMSI_H__

namespace Amsi
{
bool IsBlockedByAmsiScan(void *flatImageBytes, COUNT_T size);
};

#endif // __AMSI_H__