事情缘起于微步发的一篇文章(https://www.ctfiot.com/91461.html),文章写的很不错,在圈子内激起不小的反响,但是文章中对怎么断掉进程链含糊其辞,只说了逆向分析的过程,并没有讲解具体要怎么实现。
圈子里有同学问我是否有时间进行一个详细的分析,并给出稳定的利用呢?经过我的逆向和调试,微步的文章分析的并不够彻底,并且利用方式也并不稳定,下面是我就详细分享一下我的分析过程并提供一个更稳定的利用方法。
Windows给快捷方式提供了一个快捷键,如下图所示:
这个快捷键是全局的,当用户输入如上快捷键的时候会调用到Explorer.exe
的CTray::v_WndProc
函数,这是一个消息处理函数:
当uMsg
是 WM_HOTKEY
的时候,调用函数CTray::_HandleHotKey
进行处理。
这里的v5就是此消息的wParam,这个其实是一个id,根据此id去搜索当前程序中保存的一个DSA对象,从中获取到当前快捷键绑定的文件路径信息,然后进行调用。
这个nid是怎么生成的就非常关键,只要知道这个nid的值,我们就可以直接向这个窗口发消息了。接下来的核心就是分析这个nid是否能够从外部读取或者控制。
经过调试分析,我发现explorer.exe的快捷键也并没有什么神秘的,它跟普通软件注册全局快捷键并无区别,我们都可以使用openark看到。
那好了,我们可以直接对 RegisterHotKey
下断点,然后添加快捷键,就会看到调用掉。
看到这个id是传进来的,我们继续向上跟踪一层。
继续看一下函数CTray::_HotkeyAddCached
返回值是CTray::_HotkeyGetFreeItemIndex
产生的,我们继续跟踪:
其实这里的HDSA
是CTray
的一个成员,里面保存了当前注册的所有快捷键对应的信息,每次新的快捷键注册都会新创建一个,并且以递增的id为索引。
看到这里其实心里有点凉,这里的id保存在CTray
对象的一个成员变量中,所以我们从其他进程是无法获取也无法控制,除非我们可以解析explorer.exe
进程的内存空间,找到此对象。但是这并不现实。那要怎么办呢?
刚才openark不是可以列出系统所有的快捷键以及对应的热键ID呀?
我们看它是如何实现的不就得了?赶紧下载了openark的源代码进行分析,很快我就找到了关键代码:
if (!HotkeyTable) SearchHotkeyTable(HotkeyTable);
KdPrint(("HotkeyTable:%llx\n", HotkeyTable));
if (HotkeyTable != NULL) {
ULONG presize = sizeof(HOTKEY_ITEM) * 1024;
items = (PHOTKEY_ITEM)ExAllocatePool(NonPagedPool, presize);
RtlZeroMemory(items, presize);
if (items) {
DumpHotkeyTable(HotkeyTable, items, count);
if (count) {
ULONG size = sizeof(HOTKEY_INFO) + (count - 1) * sizeof(HOTKEY_ITEM);
if (size > outlen) {
irp->IoStatus.Information = size;
ExFreePool(items);
return STATUS_BUFFER_OVERFLOW;
}
auto info = (PHOTKEY_INFO)outbuf;
info->count = count;
for (ULONG i = 0; i < count; i++) {
info->items[i] = items[i];
}
irp->IoStatus.Information = size;
status = STATUS_SUCCESS;
}
ExFreePool(items);
}
}
主要使用的是SearchHotkeyTable
函数,但是此函数只能在内核里调用呀,用户态并没有其他办法能够读取到hotkeytable
这个结构,所以并无卵用。
通过上述分析,我们至少有如下结论:
虽然不知道nid的确切值,但是能不能知道nid的大概范围呢?
什么意思,我解释一下我的想法:比如我向explorer.exe中注册100个不同的快捷键,然后这些快捷键都指向同一个路径,然后我去调用nid=80的情况,是不是一定可以稳定触发?其实我们大概率不需要注册100个那么多,注册10个就足够了。
那注册快捷键的过程是否可以控制呢?继续看CTray::_ShortcutRegisterHotkey
的调用者,
看到这里我异常兴奋呀,原来也是一个消息呀,umsg是0x4e9
,wparam
就是对应的快捷键,lparam
是路径对应的一个值:
__int64 __fastcall CTray::_ShortcutRegisterHotkey(CTray *this, HWND a2, unsigned __int16 a3, ATOM a4)
{
unsigned __int16 v6; // ax
struct _ITEMIDLIST_ABSOLUTE *v7; // r11
int v8; // eax
WCHAR Buffer[264]; // [rsp+20h] [rbp-228h] BYREF if ( !GlobalGetAtomNameW(a4, Buffer, 260) )
return 0i64;
if ( ILCreateFromPathW(Buffer) )
{
v6 = _MapHotkeyToGlobalHotkey(a3);
v8 = CTray::_HotkeyAddCached(this, v6, v7);
if ( v8 != -1 )
CTray::_RegisterHotkey(this, *((HWND *)this + 1), v8);
}
return 1i64;
}
wparam
跟快捷键的值的转换函数如下:
WORD _MapHotkeyToGlobalHotkey(WORD wHotkey)
{
UINT nMod = 0; // Map the modifiers.
if (HIBYTE(wHotkey) & HOTKEYF_SHIFT)
nMod |= MOD_SHIFT;
if (HIBYTE(wHotkey) & HOTKEYF_CONTROL)
nMod |= MOD_CONTROL;
if (HIBYTE(wHotkey) & HOTKEYF_ALT)
nMod |= MOD_ALT;
UINT nVirtKey = LOBYTE(wHotkey);
return (WORD)((nMod*256) + nVirtKey);
}
lparam
转换为路径是使用函数GlobalGetAtomName
,这是一个系统全局的id和字符串的存储表,所有进程都是可以向系统注册的(相关文档https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globalgetatomnamea),但是这里有一个坑,调用GlobalGetAtomName
时候一直返回error: 5,我调试了一下午才解决了这个问题。
接下来思路有了,步骤如下:
c:\windows\system32\cmd.exe
,获取其id为atomid
;explorer.exe
发送0x4e9
的消息,wparam
为快捷键值,lparam
为atomidc:\windows\system32\cmd.exe
explorer.exe
发送0x312
的消息,wparam为8,就会成功触发执行cmd.exe
。其实最后发现我们根本就没有必要去创建快捷方式,简单发几条消息就搞定了。如果想深入了解相关的分析,欢迎加入知识星球交流。
我们的知识星球"安全的矛与盾"是一个既讲攻击也讲防御,开放的、前沿的安全技术分享社区。在这里你不仅可以学习到最新的攻击方法与逃避检测的技术,也可以学到最全面的安全防御体系,了解入侵检测、攻击防护系统的原理与实践。站在攻与防不同的视角看问题,提高自己对安全的理解和深度,做到: 知攻、知守、知进退;有矛、有盾、有安全。
更多的干货内容,更深入的技术交流,尽在知识星球“安全的矛与盾”,欢迎大家扫码加入!有问题请咨询微信 Manliness_man