spo0lsv.exe
3CE8412544B47D544357C5BE42FBE704
23FC53530BAACCFC4E83E3A74ADB219958B8E665
AFA1B975
通常,我们拿到病毒文件的第一件事是查看其文件信息,主要是看它是否加壳。有许多软件能查到壳的信息,这里我选择的是 PEiD v0.95。从下图可以看出,该文件已加壳。壳的信息是 FSG 2.0,这是一个压缩壳。
脱壳。这里我们选择“手工”脱壳,使用的是 x64dbg。先用 x64dbg 打开病毒文件(说明:因为当前是 Windows 7 32 位系统,所以安装的是 32 位的 x64dbg,即 x32dbg),然后按 F9,来到壳的入口点。
单步几次,先不进入第一个 call
,往下翻,找到注释(反汇编窗口的右边)有“LoadLibraryA”和“GetProcAddress”的地方。具体分析见下图。
在上图中标注“跳转到真正的入口点”的 jmp
处按 F4,然后单步,即可到达真正的入口点。
找到真正的入口点后即可脱壳。这里使用 x64dbg 的插件 Scylla,当然,也可用别的工具。
在弹出的 Scylla 对话框中,如图所示的步骤操作即可。(第一步:自动获取 IAT;第二步:获取导入表;第三步:Dump;第四步:修复 IAT)
脱壳成功。
使用 PEiD、Exeinfo PE 等工具查看脱壳后的病毒文件信息。
使用 LordPE 查看脱壳后的病毒文件信息。
因为这是一个 Delphi 程序,所以还可使用IDR获取敏感字符串信息,导出 map 文件(说明:IDA 加载签名文件后也可导出 map 文件),在之后的详细分析中,可用 Ollydbg 导入这个 map 文件,这样 Ollydbg 也可识别 Delphi 的字符串。
总结:上述步骤是在获取病毒样本之后对其的初步分析,关键在于查壳和脱壳,由于本次分析的病毒文件使用的壳较为简单,故并未使用专业的脱壳软件。还有就是,我们知道了这是一个 Delphi 程序,也就知道了它的调用约定等信息,便能更好地助益我们后面的详细分析。
这里使用火绒剑这款工具查看病毒的行为。请注意,下述操作须在虚拟机等环境中进行,千万不要在物理机中操作,以免造成不必要的损失。在虚拟机中操作前,务必先保存快照,一来虚拟机以后可能还会用到,此举可避免无谓的重装,二来后面的分析也要回到无毒的环境进行。保存快照后,把病毒文件拖入火绒剑,点击“确定”。这时病毒程序开始运行。
由下图可看出,病毒的行为不少。此时可先过滤一部分动作,每次只选择同一类型的行为,避免杂乱无章和手忙脚乱。接下来,便正式开始分析。
先分析第一个大项 —— “执行监控”。子项有“模块加载”“进程退出”“进程启动”,都是很重要的,所以全选。点击“确定”。
由下图可看出,病毒的这一类型的行为不算太多,我们可逐一查看、分析。
通过上一步,我们可总结出病毒的两种主要行为:一、退出自身进程,创建“spo0lsv.exe”进程;二、取消网络共享。
分析第二个大项 —— “文件监控”。由于子项太多,且大多不需要详细分析,故只选择如下几项。点击“确定”。
由下图可看出,虽然只选择了“文件监控”的几个子项,但病毒的行为依然不少,可见,这个病毒集中的“火力”之一便是文件操作。我们可一一点开这些行为,具体分析。
经过上一步的细致分析,可将熊猫烧香病毒在文件操作方面的行为总结出来:在“C:\Windows\System32\drivers”目录创建“spo0lsv.exe”,设置属性,修改文件;在每个目录创建“Desktop_.ini”,设置隐藏等属性,修改文件;设置每个可执行文件的属性,并修改文件;在 C 盘根目录创建“setup.exe”,设置隐藏等属性,修改文件;在 C 盘根目录创建“autorun.inf”(用于双击磁盘后自动运行指定文件,这里是“setup.exe”),设置隐藏等属性,修改文件。
分析第三个大项 —— “注册表监控”。同样的,我们只需选择“删除注册表项值” “设置注册表项值”“创建注册表键”这三个子项逐一分析即可。点击“确定”。
由下图可看出,熊猫烧香病毒删除了当时各大杀毒软件的开机启动项,这样,病毒就可以肆无忌惮了。往下翻,可看出又重复了上述行为,再看“时间”,可推测使用了定时器,每隔一段时间即重复删除。
由下图可看出,熊猫烧香病毒创建了自身的注册表键。
由下图可看出,熊猫烧香病毒在“HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run”中创建了“svcshare”,即设置了位于“C:\Windows\system32\drivers”的“spo0lsv.exe”的开机启动项。往下翻,可看出又重复了上述行为,再看“时间”,可推测使用了定时器,每隔一段时间即重复设置。
由下图可看出,熊猫烧香病毒设置了位于“HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\Folder\Hidden\SHOWALL”的“CheckedValue”为 0,实现了隐藏文件的目的,因而我们即使在“文件夹和搜索选项”中设置“显示隐藏的文件、文件夹和驱动器”也不会看到隐藏文件,这就达到了病毒隐藏自身的目的。同样的,往下翻,可看出又重复了上述行为,再看“时间”,可推测使用了定时器,每隔一段时间即重复设置。
经过如上详细分析,可将熊猫烧香病毒在注册表方面的主要行为总结出来:删除各大杀毒软件的开机启动项;创建自身开机启动项;使隐藏文件无法通过普通设置显示,也就隐藏了自身。
分析第四大项 —— “进程监控”。由于每一个操作都可能是重要的行为,为了避免遗漏,这里我们选择全部的子项进行分析。点击“确定”。
经过逐项查看,至此,我们可以将熊猫烧香病毒在进程方面的操作归纳如下:一、枚举进程;二、打开进程;三、创建进程;四、跨进程写内存;五、跨进程恢复线程;六、打开设备;七、查找窗口;八、发送窗口消息。同样的,往下翻,并且看右边的“时间”,可推测使用了定时器,每隔一段时间即重复上述行为。虽然归纳出了这些行为,但仅凭这些仍难分析出更多信息,具体的行为分析留待后面的详细分析。
分析第五大项 —— “网络监控”。同样的,由于每一个操作都可能是重要的行为,为了避免遗漏,这里我们选择全部的子项进行分析。点击“确定”。
通过逐项查看,我们可以知道熊猫烧香病毒在网络方面的操作:一、网络连接,访问139、80、443、445 等端口;二、发送数据包;三、HTTP 请求,访问当时的一些门户网站。上述第一、二个行为的目的是连接局域网里的计算机,向外发送数据包;第三个行为的目的可能是一种试探,推测其思路为:如果能访问这些网站(意味着连接了互联网),即可进行诸如下载木马等恶意行为。
我们还可通过抓包工具“WSExplorer”查看是否发送、接收数据包。由下图可看出,熊猫烧香病毒的确做出了这一行为。也可看出,主要是接收数据包。
分析第六大项 —— “行为监控”。这个项目可以说是对病毒全部行为的汇总,因而我们全选,这也能为我们之前的分析起到查漏补缺的作用。点击“确定”。
由下图可看出(往下翻仍是这些行为,只是路径和文件不同,可见,熊猫烧香病毒感染了所有的文件),该病毒主要的行为在文件操作,包括感染 PE 文件、自我复制、覆写PE 文件等。
经过上面的分析,我们已经大致了解病毒的主要行为,现总结如下:
现在开始详细分析,即借助 IDA 和 Ollydbg 两款工具,动静结合地分析熊猫烧香这个病毒。当然,主要用到的是 IDA,只有到了静态分析无法确定时才用到 Ollydbg。
用 IDA 打开病毒文件,先将不重要的部分折叠,结果如下:
一开始是大段的字符串复制,其实是病毒作者与他人的对话,不是重点,故不一一分析。
对话结束之后,会遇到两个解码函数,其后是比较字符串,类似于校验,如果没通过,则退出进程。如下:
接下来是三个 call
,经过上下文分析,推测这些可能是关键函数,需要一一详细分析。下面是分析之后得出的结论:
第一个 call
做的事如上所述,现将代码出示,其中的注释即是详细的分析过程:
jz
跳转处时修改 zf
标志位为 1,二是用 Ollydbg 再次打开位于 C:\Windows\System32\drivers
目录的 spo0lsv.exe
文件,此即病毒文件关于第一个 call
的总结:
接下来分析第二个 call
。其先调用三个函数,如下:
先分析第一个函数,其内部调用创建线程的 CreateThread
函数,故我们只需分析其线程回调函数即可。如下:
线程回调函数又调用了一个重要的函数,即遍历驱动器的函数,也有必要分析,如下:
再分析第二个函数,其内部调用 SetTimer
函数,即设置一个定时器,对于这个函数,我们也只需分析其回调函数即可。如下:
最后分析第三个函数。其内部调用 BeginThread
函数,同样的,也只需要分析其回调函数。如下:
至此,外部的第二个 call
分析完毕,现总结如下:
Desktop_.ini
文件,写入感染时间,设置隐藏等属性;setup.exe
病毒文件和 autorun.inf
自运行文件,并设置隐藏等属性;最后,分析外部的第三个 call
。其内部调用六个 SetTimer
函数,即设置六个定时器,同样的,只需要分析其回调函数或创建线程的回调函数即可。
第一个定时器的回调函数如下:
可以看出,其操作了注册表,效果如下:
其中,回调函数内部创建了一个线程,故有必要分析其线程回调函数。如下:
第二个定时器创建了一个线程,同样的,只需要分析其线程回调函数。如下:
第三个定时器创建了两个线程,而第一个线程的回调函数和第二个定时器的线程回调函数一样,不再赘述。现只分析第二个线程的回调函数。如下:
第四个定时器创建了一个线程,同样的,只分析其回调函数。如下:
上述关闭系统服务后的效果之一如下:
第五个定时器没有创建线程,其回调函数如下:
第六个定时器也没有创建线程,其回调函数如下:
综上,第三个外部 call
也已分析完毕,现将其总结如下:
***武*汉*男*生*感*染*下*载*者***
、WhBoy
结束 spo0lsv.exe
进程,可通过 cmd
输入 tasklist
和 taskkill
命令;
在“运行”中输入 msconfig
,打开启动项管理,把 svcshare
这个启动项关闭
在运行中输入regedit,打开注册表编辑器,找到如下键(或子键):HKLM\Microsoft\Windows\CurrentVersion\Explore/Advanced/Folder\Hidden\SHOWALL
,将其中的值项 CheckValue
设为 1;
在资源管理器中设置显示隐藏文件,把驱动器根目录下的 setup.exe
和 autorun.inf
文件删除;
删除 C:\Windows\system32\drivers
目录下的 spo0lsv.exe
文件;
删除各个目录下的 Desktop_.ini
文件。
main
函数:
int main()
{
DWORD dwPid = 0;
// 提权
if (!EnableDebugPrivilege(SE_DEBUG_NAME))
{
printf("提权失败!!!\n");
}
// 杀死病毒进程
if (!FindTargetProcess(L"spo0lsv.exe", &dwPid))
{
printf("获取进程PID失败!!!\n");
}
else
{
HANDLE hproc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (!TerminateProcess(hproc, 0))
{
printf("无法结束病毒进程!!!\n");
}
CloseHandle(hproc);
}
// 删除系统目录下的病毒文件 spo0lsv.exe
printf("正在删除病毒文件...\n");
DeleteSysVirus();
// 删除每个盘符下的 setup.exe和autorun.inf 文件 和每个文件夹下的 Desktop_.ini 文件
DeleteFiles();
// 检查注册表
printf("正在检查注册表...\n");
CheckRegedit();
system("pause");
}
获取权限函数:
BOOL EnableDebugPrivilege(LPCTSTR pszPrivilege)
{
HANDLE hToken = INVALID_HANDLE_VALUE;
LUID luid;
TOKEN_PRIVILEGES tp;
BOOL bRet = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
if (bRet == FALSE)
{
return bRet;
}
bRet = LookupPrivilegeValue(NULL, pszPrivilege, &luid);
if (bRet == FALSE)
{
return bRet;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
bRet = AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
return bRet;
}
遍历进程函数:
BOOL FindTargetProcess(LPCTSTR pszProcessName, DWORD *dwPid)
{
BOOL bFind = FALSE;
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE)
{
return bFind;
}
PROCESSENTRY32 pe = { 0 };
pe.dwSize = sizeof(pe);
BOOL bRet = Process32First(hProcessSnap, &pe);
while (bRet)
{
if (lstrcmp(pe.szExeFile, pszProcessName) == 0)
{
*dwPid = pe.th32ProcessID;
bFind = TRUE;
break;
}
bRet = Process32Next(hProcessSnap, &pe);
}
CloseHandle(hProcessSnap);
return bFind;
}
删除系统目录中 spo0lsv.exe
文件函数:
VOID DeleteSysVirus()
{
TCHAR szSysPath[MAX_PATH] = { 0 };
BOOL bRet;
GetSystemDirectory(szSysPath, MAX_PATH);
lstrcat(szSysPath, L"\\drivers\\spo0lsv.exe");
printf("检查硬盘中是否存在spoclsv.exe文件...\n");
if (GetFileAttributes(szSysPath) == 0xFFFFFFFF)
{
printf("spo0lsv.exe病毒文件不存在\n");
}
else
{
printf("spo0lsv.exe病毒文件存在,正在计算md5值\n");
std::string dwMd5 = getFileMD5(TCHAR2STRING(szSysPath));
// 9A76CBBEC498FDDC87F30E74A91AE7B3 是病毒的 md5 值
if (dwMd5 == "9A76CBBEC498FDDC87F30E74A91AE7B3")
{
printf("校验和验证失败\n");
}
else
{
printf("校验和验证成功,正在删除...\n");
// 去除文件的隐藏、系统以及只读属性
DWORD dwFileAttributes = GetFileAttributes(szSysPath);
dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN;
dwFileAttributes &= ~FILE_ATTRIBUTE_SYSTEM;
dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY;
SetFileAttributes(szSysPath, dwFileAttributes);
// 删除 spoclsv.exe
bRet = DeleteFile(szSysPath);
if (bRet)
{
printf("spo0lsv.exe病毒被删除!\n");
}
else
{
printf("spo0lsv.exe病毒无法删除\n");
}
}
}
}
删除各个驱动器根目录中 setup.exe
、autorun.inf
和各个目录中 Desktop_ini
文件函数:
VOID DeleteFiles()
{
TCHAR szDriverString[MAXBYTE] = { 0 };
TCHAR *pTmp = NULL;
BOOL bRet;
//获取字符串类型的驱动器列表
GetLogicalDriveStrings(MAXBYTE, szDriverString);
pTmp = szDriverString;
TCHAR szAutorunPath[MAX_PATH] = { 0 };
TCHAR szSetupPath[MAX_PATH] = { 0 };
lstrcat(szAutorunPath, pTmp);
lstrcat(szAutorunPath, L"autorun.inf");
lstrcat(szSetupPath, pTmp);
lstrcat(szSetupPath, L"setup.exe");
if (GetFileAttributes(szSetupPath) == 0xFFFFFFFF)
{
printf("setup.exe病毒文件不存在\n");
}
else
{
std::string dwMd5 = getFileMD5(TCHAR2STRING(szSetupPath));
if ((dwMd5 == "9A76CBBEC498FDDC87F30E74A91AE7B3"))
{
printf("%s校验和验证失败\n", szSetupPath);
}
else
{
printf("校验和验证成功,正在删除...\n");
// 去除文件的隐藏、系统以及只读属性
DWORD dwFileAttributes = GetFileAttributes(szSetupPath);
dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN;
dwFileAttributes &= ~FILE_ATTRIBUTE_SYSTEM;
dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY;
SetFileAttributes(szSetupPath, dwFileAttributes);
// 删除setup.exe
bRet = DeleteFile(szSetupPath);
if (bRet)
{
printf("%s病毒被删除!\n", szSetupPath);
}
else
{
printf("%s病毒无法删除!\n", szSetupPath);
}
}
}
// 去除文件的隐藏、系统以及只读属性
DWORD dwFileAttributes = GetFileAttributes(szAutorunPath);
dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN;
dwFileAttributes &= ~FILE_ATTRIBUTE_SYSTEM;
dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY;
SetFileAttributes(szAutorunPath, dwFileAttributes);
// 删除autorun.inf
bRet = DeleteFile(szAutorunPath);
if (bRet)
{
printf("%s被删除!\n", szAutorunPath);
}
else
{
printf("%s不存在或无法删除!\n", szAutorunPath);
}
// 删除Desktop_.ini
EnumFile(pTmp);
// 检查下一个盘符
pTmp += 4;
}
修复与检查注册表函数:
VOID CheckRegedit()
{
// 首先检查启动项
TCHAR RegRun[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run";
HKEY hKeyHKCU = NULL;
LONG lSize = MAXBYTE;
TCHAR cData[MAXBYTE] = { 0 };
long lRet = RegOpenKey(HKEY_CURRENT_USER, RegRun, &hKeyHKCU);
if (lRet == ERROR_SUCCESS)
{
lRet = RegQueryValueEx(hKeyHKCU, L"svcshare", NULL, NULL, (unsigned char*)cData, (unsigned long*)&lSize);
if (lRet == ERROR_SUCCESS)
{
lRet = RegDeleteValue(hKeyHKCU, L"svcshare");
if (lRet == ERROR_SUCCESS)
{
printf("注册表启动项中的病毒信息已删除!\n");
}
else
{
printf("注册表启动项中的病毒信息无法删除\n");
}
}
else
{
printf("注册表启动项中不存在病毒信息\n");
}
RegCloseKey(hKeyHKCU);
}
else
{
printf("注册表启动项信息读取失败\n");
}
// 接下来修复文件的隐藏显示,需要将 CheckedValue 的值设置为 1
TCHAR RegHide[] = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced\\Folder\\Hidden\\SHOWALL";
HKEY hKeyHKLM = NULL;
DWORD dwFlag = 1;
long lRetHide = RegOpenKey(HKEY_LOCAL_MACHINE, RegHide, &hKeyHKLM);
if (lRetHide == ERROR_SUCCESS)
{
printf("检测注册表的文件隐藏选项...\n");
if (ERROR_SUCCESS == RegSetValueEx(hKeyHKLM, L"CheckedValue", 0, REG_DWORD, (CONST BYTE*) & dwFlag, 4))
{
printf("注册表文件隐藏修复完毕!\n");
}
else
{
printf("无法恢复注册表的文件隐藏选项\n");
}
}
}
遍历文件函数:
VOID EnumFile(PTCHAR szPath)
{
TCHAR szBuffPath[MAX_PATH] = {};
TCHAR szFilePath[MAX_PATH] = {};
_stprintf_s(szBuffPath, _T("%s\\*.*"), szPath);
WIN32_FIND_DATA wfd = {};
HANDLE hFindFile = FindFirstFile(szBuffPath, &wfd);
if (hFindFile != INVALID_HANDLE_VALUE)
{
do {
TCHAR szPahtAll[MAX_PATH] = {};
_stprintf_s(szPahtAll, _T("%s\\%s"), szPath, wfd.cFileName);
//过滤特殊目录 . ..
if (lstrcmp(_T("."), wfd.cFileName) == 0 || lstrcmp(_T(".."), wfd.cFileName) == 0)
{
continue;
}
//判断是文件还是目录
if (wfd.dwFileAttributes& FILE_ATTRIBUTE_DIRECTORY)
{
//printf("目录:%S\n", wfd.cFileName);
//递归调用自己
EnumFile(szPahtAll);
}
else {
if (!lstrcmp(wfd.cFileName, L"Desktop_.ini"))
{
// 去除文件的隐藏、系统以及只读属性
DWORD dwFileAttributes = GetFileAttributes(szPahtAll);
dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN;
dwFileAttributes &= ~FILE_ATTRIBUTE_SYSTEM;
dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY;
SetFileAttributes(szPahtAll, dwFileAttributes);
// 删除Desktop_.ini
if (DeleteFile(szPahtAll))
{
printf("%S已被删除\n", szPahtAll);
continue;
}
}
PTSTR Suffix = PathFindExtensionW(wfd.cFileName);
if (!lstrcmp(Suffix, L".exe"))
{
FixFile(szPahtAll);
}
}
} while (FindNextFile(hFindFile, &wfd));
}
}
修复 PE 文件函数:
VOID FixFile(PTCHAR szPath)
{
HANDLE hFile = CreateFileW(
szPath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return;
}
DWORD dwSize = GetFileSize(hFile, NULL);
BYTE* pFile = new BYTE[dwSize];
if (pFile == NULL)
{
return;
}
DWORD dwCount = 0;
ReadFile(hFile, pFile, dwSize, &dwCount, NULL);
BYTE* p = pFile;
DWORD dwNum = 0;
DWORD dwSourceSize = 0;
if (pFile[dwCount-1] == 0x01)
{
p = pFile + dwCount - 1;
while (*p-- != 0x00)
dwNum++;
p += 2;
if (strncmp((char*)p, "WhBoy", 5))
{
return;
}
pFile[dwCount-1] = 0x00;
while (*p++ != 0x02);
dwSourceSize = atoi((char*)p);
// 偏移等于 = 感染文件总大小 - 原文件大小 - 感染标识字符串长度 - 1
DWORD dwOffset = dwSize - dwSourceSize - dwNum - 1;
pFile += dwOffset;
//SetFilePointer(hFile, NULL, NULL, FILE_BEGIN);
//WriteFile(hFile, pFile, dwSourceSize, &dwNum, NULL);
CloseHandle(hFile);
HANDLE hFile = CreateFileW(
szPath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
SetFilePointer(hFile, NULL, NULL, FILE_BEGIN);
WriteFile(hFile, pFile, dwSourceSize, &dwNum, NULL);
}
CloseHandle(hFile);
}