Skip to content

Commit

Permalink
Port AMSI scanning for assembly loads (dotnet/coreclr#23231)
Browse files Browse the repository at this point in the history
* Port AMSI scanning for assembly loads

* Define PLATFORM_WINDOWS for Windows build

* Remove check for LOAD_LIBRARY_SEARCH_SYSTEM32 support


Commit migrated from dotnet/coreclr@3a02976
  • Loading branch information
elinor-fung authored and jkotas committed Mar 15, 2019
1 parent 1407ff7 commit b20ed44
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 46 deletions.
30 changes: 16 additions & 14 deletions src/coreclr/configurecompiler.cmake
Original file line number Diff line number Diff line change
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/coreclr/src/vm/CMakeLists.txt
Original file line number Diff line number Diff line change
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/coreclr/src/vm/amsi.cpp
Original file line number Diff line number Diff line change
@@ -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);
if (hr == S_OK && (AmsiResultIsMalware(result) || AmsiResultIsBlockedByAdmin(result)))
return true;

return false;
}
16 changes: 16 additions & 0 deletions src/coreclr/src/vm/amsi.h
Original file line number Diff line number Diff line change
@@ -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__
Loading

0 comments on commit b20ed44

Please sign in to comment.