 Alright, so yeah, this is uh, Malproxy, leave your malware at home, so here we go. Thank you. Hi everyone, we really appreciate your time coming here today. Uh, so my name is Amit and with me is my colleague Gila. Both come from Israel and as you understand this is our first time in DEF CON, speaking in DEF CON. Uh, we both come from a start up company called XM Cyber and we really do offensive cyber. So uh, we can talk much about our background, that's why we decided to put some cool trivia stuff about us. But let's move on to the actual topic. So today we're going to show you a new and cool technique to execute malicious code by passing existing endpoint security solutions. Because as attackers we continuously try to find new ways to execute our malicious code under the presence of those solutions. I know there are many existing techniques like phallus viruses or executable packers, but most of those techniques require modifications on the malicious code side. So it is harder for the attackers to implement them over and over again to every piece of malicious code. So here we present a generic technique that simply proxies system API calls over the network so the malicious code is never present on the target machine, on the victim. And because it's never there, most of the defenses and security mechanisms implemented inside the endpoint security solutions will simply not detect the code. And because the code is unaware of that proxying operation, uh, no modifications are required in it at all. So in this talk today we'll cover some basic protection mechanisms that exist in most of the endpoint security solutions in the market today just to keep everyone on the same page. Then we'll uh, describe our mild proxy solution in detail including a demo and we'll end of course with some mitigations. So why do we actually speak specifically about endpoint security solutions? Mainly because they are a major part of every organization security stack. Those solutions evolved over time alongside the attackers because sometimes the attackers successfully penetrated the defenses so the security community had to keep up the pace and put another defenses, more advanced defenses to cope with the new attacks. But it's like a cat and mouse game where the cat keeps tracing the mouse and the mouse keeps getting away. But it is an unfair game because the mouse has to find a single way to escape the cat while the cat has to predict all the possible ways of escape of the mouse uh, to block it from escaping before it happens. So what do we actually know about the current status of endpoint security solutions? For many of us it's just a black box with a lot of fairy dust, some cool UI, and of course machine learning inside that consumes most of our CPU on the endpoints. But most of us don't actually know what happens inside. But in the bottom line we know that there are cyber attacks in the world and there are successful penetrations of most of the defenses. So we can actually sum up the current status of endpoint security solutions in one short sentence. Not great, not terrible. But let's understand why. So thanks to the cat and mouse game over the years the endpoint security solutions evolved significantly and developed more and more advanced protection mechanisms. So historically we can start with the first protection mechanism that was implemented which is the static signatures. Those look on what we called IOC's, indications of compromise. Those can be binary sequences in a file or in a memory dump or simply strings, readable springs from a file. The next mechanisms that came to close some gaps in the static signature mechanisms is the heuristic rules. Those don't look on the actual uh, data inside a file but look on the different properties from a higher perspective. It can be where the file is located, what API is it uses or whether it is encrypted or not and so on. Those rules basically calculate some sort of heuristic score. And in many endpoint security solutions a negative score means malicious and positive score means innocent. So that's the whole purpose to give some score and decide whether a file is malicious based on this score. The last mechanism is the behavioral signatures. And those come to actually look on the uh, impact that a piece of code uh, does on a system and not on the actual implementation. So every piece of code that runs on a system interacts with the operating system in some way through API calls. So when we monitor those API calls and look for some malicious patterns in that activity log we may get uh, we may verdict some of those uh, pieces of code without actually knowing them in advance. So it deals with the unknown malware. So the solution that we present here actually handles the first two mechanisms successfully and it gives very hard time for the behavioral signatures to catch us. So let's dive into the first category, the static signatures to understand them better. And let's take this piece of code as an example of malicious code. I know it's not but let's think it is. See it defines some string and calls printf. And now I would like to create a static signatures that verdicts that piece of code but not other pieces of code. So I can use a technology called YARA and create some rules and test those rules on various binary files. I didn't actually write this YARA rule. I took it from some website online but you can see it defines some strings and a binary sequence which is an ELF header. This is a Unix based executable. And the condition is that the ELF header exists in the first four bytes of the file and there is either the ADM string or corp string. So if we compile the code that we saw before and apply the YARA rule on it, we can see that YARA verdicts the file because we have the ELF header and we have the ADM string inside some offset in the file. So that's a very basic static signature. But now let's create another static signature that captures Mimicats. We love Mimicats and we use it as demonstration across this talk. So those six strings I extracted from Mimicats binary. So the question is can we create a YARA rule based on those six strings only that verdicts all the files that contains those six strings? So the answer is not because even this presentation file, this PDF file actually contains those strings. And as far as I know it is not Mimicats. So we cannot simply verdict the file based on some stupid strings. We have to be very gentle and very careful when we create static signatures because otherwise we might get false positives. And false positives are one of the worst nightmares of the security researchers that create the static signatures. Let's move on to the second category, the heuristic rules. And as I said, those look on different properties or features in a file, in the file system or in the memory. And they try to classify for each property whether it is malicious or innocent. And that's the whole purpose of those heuristic rules. To distinguish between malicious properties and innocent properties. Some heuristic rules can be generated by a human being via security researcher while other can be generated by some fancy AI algorithm. It doesn't matter. The whole purpose is to create some sort of heuristic heuristic score based on a combination of different properties. So on the screen you can see an example of an analysis of a PE file, portable executable file in Windows. And you can see we have the dot text section which is the code, the dot data which is the data of course, and another custom section called UPX2. And we know that UPX is a famous executable packer. So as you can see there are some red flags in this analysis. We can look at the text section and you can see that the row size is different than the virtual size. Most of the time it does, but the values are more close together. In our case the row size is zero bytes long. So it sounds suspicious. Another red flag is that the text section is actually writable. And actually there is no reason that the text section should be writable. It just contains code. Another red flag is the existence of the UPX section. Because we know that UPX actually takes some blob of binary data from the data section, decrypts the data, and executes it as a shell code or some sort of binary code. So we know that this application, this code actually hides its true nature from us. So if we are a security, an endpoint security solution, and we want to inspect what this file actually does, we know that this true, the true nature of this file is hidden from us. On the other hand, if we have a digitally signed file on the file system that imports some API functions that interact with the user, we may assume that this file is innocent because it has many innocent features inside it. So over the years we know that malicious files, malicious pieces of code actually penetrated the static signatures and the heuristic rules. And that's what the behavioral signatures came to solve. They look on the actual activity that a piece of code does on the system in a love environment or in a sandbox, it doesn't matter, and analyzes that activity log of a single thread or an entire process in order to look for malicious patterns inside. So similarly to the static signature case, finding, determining whether a pattern is malicious or not is very delicate because we might get false positives also here. On the screen you can see an example of a Mimicats, a logon passwords command execution. So you see we call antiquary system information to get a process list, then we open a handle to Elsas, and later we will read its memory using grid post memory. So can we take this activity log and assume it is malicious wherever we find it? So the answer is no. Similarly to the static signatures because otherwise we might get false positives. Microsoft exported this, those APIs for a reason. It is legitimate to open a handle to another process and read its memory. Otherwise, those APIs will not be exposed to the user. A very good example of the long way that the behavioral signatures have to go before they become more trustworthy can be seen in one of the, of Benjamin Delphi's latest posts a few weeks ago. It discovered that if you take Calc.exe and add some commanding arguments that are related to Mimicats, Windows Defender actually blocks Calc.exe execution and verdicts it as Mimicats. So, and as we know, Calc.exe is one of the most innocent executables I know. So there is a long way for the antiviruses to go before we can trust the behavioral signature mechanism. Now we'll I will show you how we can bypass those security mechanisms that they talked about. Thank you, Amit. So for every protection mechanism there are ways to go around it and endpoint protection solutions are no exception. We can change the properties, IOCs and behavior of every malicious file thus making it undetected by security solutions. But those changes require a lot of manual work and expertise from the attacker. It has to be done for every malicious file separately. So it's not feasible or scalable to do it over and over again. We can also use other techniques that Amit mentioned to bypass security solutions but they may require to do some modifications in the malware code. It will present our technique to bypass security solutions. We call this technique malproxy and you will understand why shortly. So now we know that endpoint protection solutions try to find our code and its properties, analyze and verdict it. So let's make it impossible for those solutions to even look at our code. We can simply avoid deploying the malicious code on the target machine by separating the code from its interaction with operating system. Let's assume we have a machine with operating system that executes a process with malicious code inside it. The code interacts with operating system using API calls as you can see in the sketch. The malicious code is present on the target machine so it can be captured by any security solution. Can we migrate the malicious code somewhere? It won't be detected by those solutions? Of course we can. Our solution allows to generically load and execute malicious code and send the API calls it executes to the target side over the network. For the malicious code it seems like it is executed on the target system as it cannot otherwise. It interacts with the operating system using API calls thus emulating them makes it looks like it is executed on the target system. Our solution consists of two major components, the attacker side stab and the target side stab. The attacker side loads and executes the malicious code, it controls its API calls and redirects them over a network tunnel to the target side. The target code looks innocent and doesn't contain any malicious activity pre-coded in it. It receives the API calls and arguments, executes those calls and returns the results back to the attacker side. Then the attacker side returns the results back to the malicious code the same way they would be returned if the malicious code had called those requests locally. It's important to emphasize that the malicious code is totally unaware to the long journey that the response went through until it reached its final destination. Let's take create file function for example. Our malicious code calls it and malproxy captures that call and create a detailed message for the attacker for the target side which contains the create file function name and the path of blah dot xt. Then malproxy sends this message to the target side which parses it and executes create file function as instructed. After the call, the operating system returns the handle to that file and now malproxy has to send this handle back to that attacker side. Then malproxy returns the handle back to the malicious code and as far as it is concerned, we now have a valid handle to blah dot xt. To describe our solution in depth, let's review on some fundamental key terms. We all know what the system call is and here is a schematic sketch of how API call is executed. The top function is called by the user's code and it can call other functions from other libraries if needed. Sometimes those calls reach the native API layer which makes the transition between the user mode and kernel mode. After reaching the kernel, the operating system executes the needed operations to fulfill the task and return the results back to the user. To proxy a system call without making any adjustment to the malicious code, we can implement our malproxy in any layer of the stack. We can get inside the transition between the user mode and kernel mode and proxy any system call that we want. However, it was proved quite difficult. After experimenting with this proxy logic, we found out it's much easier and convenient to intervene and implement our malproxy in the top layers of the API call and not necessarily in the native API layer. Every API call that doesn't trigger a system call is irrelevant to our solution because it executes the same flow on both the attacker side and the target side. So let's let those functions execute on the attacker side. Why waste pretty good bytes on the wire for any unnecessary operation? So how do we proxy a system call or any API function call in that matter? We use a technique called hooking which redirects the system call to our code instead of the real operating system implementation. A full control over any API function allows us to inspect all arguments, change them if needed and construct a message that will be sent to the target side over the network. Moreover, we can change the value that is returned from the function. There are many techniques to perform hooking and this talk is too short to describe them all. We use a technique called import address table hooking technique which is very common and easy to implement thanks to the fact that we have full control over the malicious code load and execution. The import address table describes all the functions that are used by the Windows executable while loading the Windows loader resolves the address of every imported API function and places those addresses in the import address table structure. Using this technique of changing the addresses of API functions allows us to separate between the malicious code logic and its interaction with the operating system. The loading and hooking of the malicious code is done on the attacker side so it's not monitored by any security solution. Now let's inspect the structure of API function call. Every API function consists of three main properties. The return value type, the calling convention and the function arguments. The return value type is the type of the value that will be returned from the function and it can be either primitive, a well known structure or a pointer to some address. The calling convention defines how the arguments are passed from the caller to the callee and every argument can be also either a primitive, a well known structure or a pointer and can be treated as an input and output or combination of both. To proxy a system call we need to handle each of those properties by serializing and deserializing them on both the attacker side and the target side. Now Amit will explain you how we use those concepts to proxy API function call. Thank you Hela. So now we understand that our top priority is to proxy win API functions. And to do that we have to pay close attention to all the elements that Hela mentioned in the function prototype. The return value, the calling convention but most importantly the arguments. Because the argument, the arguments may be very hard to handle. We might have many types of arguments. Some may be input, some may be output or combination. Some may be structures or pointers to some structures that contains other pointers to structures. The memory might not be, might not even be continuous in the virtual address space. So our serialization and deserialization logic on both the attacker side and the target side have to take all the different aspects into account in order to handle all possible argument types. So once we implement all the different argument types we can simply add more and more API calls and it is quite trivial because once we cover all the different argument types it's very easy to add more API calls. And as there are many argument types to handle and our time here is short I chose to focus on what I think is one of the interesting cases and is handling memory buffers in API calls. We can actually divide the API, the Windows APIs into two main groups. Those who handle user allocated memory buffers or the user pre-allocates the buffer before calling the API function and system allocated memory buffers or the system call itself is responsible for the memory allocation and afterwards when the user is done using the buffer is responsible to call another API function for leasing that memory. So when we do the proxying between the target side and the attacker side of those memory buffers we might keep, we must keep a translation between the virtual address space on the attacker side and the virtual address space on the target side because we cannot promise that the same addresses will be allocated for both sides. So when we get some address from the target side that was allocated by some system call and we use that address on the attacker side of course the translated address the malicious code will later call the release function of that buffer but the address the malicious code will use will be the address of the attacker side so we have to translate it again for the target side to actually release the resources otherwise we'll leak those resources. So in order to understand it better we'll look at some example in this we chose to show you anti-query information process. First because it is used by Mimicats as you will see later and second you can see in the third argument it uses an opaque buffer we do not know its structure in advance and the structure is set by the value of the second argument the processing information class. You can see we have three input arguments and two output arguments and actually those output arguments are not input as well so they are output only and the system will override whatever data stored inside. So as far as we're concerned in the request side we can treat those buffers as complete garbage we don't have to serialize it to serialize the data into the request message. Now let's see how we handle each one of those arguments. So the first argument is the process handle. A handle is simply an index in the object table of some process and once we proxy this call to the target side the handle must have meaning on the target side. So we probably got it from some previously proxy API call for example to open process or create process or whatever API that returns us a handle to a process. This handle is numeric value so it's very easy to serialize it into the request message and as I said it doesn't have any meaning on the attacker side. It has meaning only on the target side. So we serialize it into the request message. Now we serialize also the process information class. It's also a numeric value so we serialize it easily into the request message but we have to keep to remember what value it is in order to deal with the buffer and the third argument later on. As I said the buffer, the processing information buffer, we can treat it as garbage because its data will be overridden by the system API. So we don't have to add it into the request message at all. Now the process information length is the size of that buffer. We do have to serialize it before as you'll see later we do have to allocate a buffer on the target side for the actual system API call. The return length is also an output only argument so we don't have to serialize it into the request message. Now we have all the data we need in the request and we can send it to the target side where we deserialize all the input arguments and now we have to allocate enough memory space for the output arguments. The third argument size, we know it because it is set in the fourth argument value. The fifth argument size is fixed. It's an unsigned long it's always four bytes long so we can allocate enough memory for it and call the actual API. Now once we the API returns we need to serialize the output arguments to the response message. So let's start with the easiest case which is the return length because we know its size in advance, we know the structure is simply an unsigned long so we can serialize it into the response message. But handling the process information might be difficult because it might be a complex data structure that has pointers to other data structures that has more pointers to other data structures. And this memory block might not be, it might not even be continuous in the virtual address space. So we do have to take all the relevant information and serialize it into one continuous memory block in the response message and we have to implement the deserialization logic to deserialize and rebuild the complex data structure back on the attacker side. All is left is to serialize the return value anti status which is also defined to be numeric value. And now we are ready to send the message back to the attacker side where we need to deserialize all the output arguments, rebuild the complex data structure and return the values back to the color code. And the color code is totally unaware of those of that proxying operation. As far as it's concerned it called the API function and got the results back where it expected them to be. Now let's have a quick recap of what we have here. We have two stabs, the attacker side stab and the target sized stab that proxies system API calls over a network tunnel. The attacker side loads the malicious codes and hooks some of the API functions that interact with the target side operating system. The target side receives the commands that should be executed, the API calls and the arguments and actually makes the call to the target operating system. All the results, the output arguments and the return value are serialized back into the response message and sent to the attacker side which deserializes them and returns them to the original color code. And as I said the color is unaware of those proxying operation so we don't have to modify it at all. We just, we can just take a pre-compiled malicious code and execute it. This flow happens over and over again for every API that is called nproxit. So now we understand how a single API call can be proxied. Now let's have a look on the scope of an entire process. And for that we chose to show you again Mimicats because first we love it and second it is uh very dictated by most of the antivirus engines in VirusDOTO. So it is a very good challenge to execute its logic without being detected. So in order to run uh specifically the login password command in Mimicats uh you can see we analyzed some of the API calls that are used by the login passwords command. And in fact we don't have to proxy them all. We can just proxy those functions that interact with the target operating system. So let's simplify the execution flow of the login passwords command. So it can be simplified to this flow of API calls. And let's inspect them one by one what APIs we need to proxy and what APIs we don't need to proxy. So first RTL adjust privilege actually gives us an elevated token and we need an elevated token in order to interact with the target side ELSAs. And because we need to interact with the target side ELSAs we need an elevated token on the target side. So we do need to proxy these API call to the target. Then we need to enumerate all the processes in the target system to get some metadata about ELSAs because we need to interact with the target side ELSAs. So we call antiquary system information with some flags that indicate that the process list should be returned from that API. And we get all the data we need inside a user allocated buffer. We proxy that API call to get the information from the target side. Now once we have all the information we need in the user allocated buffer we can just look for ELSAs in the metadata list simply by comparing strings using RTL equal UNICode string. And this can be done entirely locally on the attacker side. Then we call open process with the process ID that we extracted from the metadata. And the open process is called on the target side because we need a handle to the target side ELSAs. And we get back that handle. And as you remember the handle has a meaning only on the target side. As far as the attacker is concerned this is some magical value that doesn't have any meaning in the process besides calling other API functions. For example, antiquary information process. Mimicats uses that to get the location of the process environment block the PEB. So it queries the PEB location and based on the data stored in that PEB it calculates what memory buffers are of interest and contain potential credentials in ELSAs. Then all those memory buffers are read using grid process memory. It reads memory form and distant process. So we need to proxy this API as well because we need to query the memory of the target side ELSAs. Once Mimicats contains all the information needed inside it's virtual outer space on the attacker side we can simply decrypt the data. And now we got the password we need and we won, we found the system. So this is all nice in theory but let's see how it works in real life. And for that we installed two Windows 10 machines. One is the attacker and one is the victim. The target. And the victim has a window and has the latest Windows 10 with Windows Defender active with the latest security patches. And our goal is to execute Mimicats login password operation without being detected by Windows Defender. So in this demonstration I will simply double click the target side stub but in real life it will be executed by some explosion exploitation flow or whatever. It doesn't matter for the demonstration purposes but keep in mind that we do have to execute our code on the target side. So let's see how it actually works. So this is our target side. And as you can see we have Windows Defender installed. Everything is active. All the checks are green. And we have zero threats in the history and the settings are all active. We didn't define any exclusions or whatever. And now we launch our target side stub. To do that we run it as an admin to get an elevated token later. And as far as we know Windows Defender didn't detect anything. Now let's look on the attacker side. You can see this is the command line on the attacker side. We run on the attacker machine under the attacker user. And we want to run our attacker side stub. And to do that we simply launch it with the command line arguments of the path to the actual Mimicats executable downloaded from Github. And the host name and TCP port of the target side. So now let's look how both sides interact with one another. On the left side you can see the attacker and on the right side you see the victim, the target. And every operation that is executed by the attacker will be reflected on the target side. So when we launch Mimicats, many DLLs are loaded and those DLL loads are reflected on the right hand side. Now the first command that we execute is privilege debug. This will give us the elevated token we need. And it is reflected by calling RTL adjust privilege on the target side. You can see it in the bottom line. Now the second command that we execute is the secure LSA logon passwords command. And this actually opens a handle to else as read its memory and parses the credentials. So once we launch it you'll see that many API functions are called. And while they're called, the results are already passed on the attacker side. So now we have the credentials we need. We can scroll and inspect them and find whatever we look for. And in this example this is the NTLM hash of the victim account. So we got what we wanted. But did we do it silently? So let's inspect the Windows Defender. As far as we can see we have zero threats in history still and we do not have any quarantined items or threats detected. But most importantly we do not, we didn't get blocked by Windows Defender. We successfully executed whatever we wanted without being detected. So now I will talk about how we implemented our code to bypass those security mechanisms. So all of the fun that we showed so far runs under the radar of many security solutions. Why? Because our targets tab looks innocent. And as we know, naive idiots are not threat. So why do we run under the radar? This is not a magic. We now know what are the major mechanisms existing in most of the security solutions to detect malicious code. Our targets tab avoids the static signatures detection because it doesn't have any malicious logic in its base form. The malicious code is never deployed on the target side. So the Mimicat's executable strings that Amit showed earlier will never be on the target operating system. Moreover, our target tab avoids the heuristic holes detection because it doesn't have any of the Mimicat's executable properties. So static signatures and heuristic holes are not a threat to us. But let's keep in mind that the behavior signatures might detect us. And this is the only mechanism that might detect our malicious activity. But those signatures are expensive, expensive to develop and expensive to execute. We executed malproxy under the leading security solutions in the market and here are the results. As you can see, in most cases malproxy executed Mimicat successfully, while in other cases security solution blocked some API call with access deny error. The operation has simply failed but no verdict was made. So we are almost the end of our talk. We have seen the limitations in existing security products and showed a way to bypass their detection mechanisms. So how can those products catch us? As we said, our ops lie on behavior signatures. Obviously, our target stab can be statically signed by its IOCs and start another race between the defense and offense players to find the best IOCs on one end and change the code to avoid detection on the other end. We believe that pretty good static signatures can be generated to detect our tool. But as we have seen during this talk, this mechanism is far from being hermetic. Another option is to improve the behavior signatures detection. As we said, in the bottom line, the operating system interacts with the target operating system. We can monitor the operations taken by the malicious code to make sure that all system calls are fully tracked. The signatures, building the log of executed system calls and the signatures which define what malicious behavior is have to become more robust. So those are the ideas that we add. But you are more than welcome to think about more ways to bypass detection on one end and detect your bypasses on the other end. You are welcome to send your ideas to the address presented now on the screen. So we want to thank our colleague Yaron Shani for pointing out this concept to us and maintaining this cool blog with lots of interesting stuff inside. We also want to mention other articles which were published many years ago and describe this proxy concept briefly. Thank you very much for coming. We really appreciate it. So we do actually will publish the code of Melproxy online on GitHub shortly. I'll just have to make a few fixes in the code and then it will be publicly available if anyone is interested. Thank you.