Worm.Parite.Residented 详细分析+专杀工具src
本帖最后由 yangjt 于 2010-8-4 19:49 编辑【文章标题】: Worm.Parite.Residented 详细分析+专杀工具src
【文章作者】: Azure
【作者邮箱】: Azure@52pojie.cn
【作者QQ号】: 325002492
【加壳方式】: UPX
【编写语言】: BC++
【使用工具】: IDA、OD
【操作平台】: Win7+WinXP Sp3(VM)
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
完蛋了……看来我的计算机水平还是菜鸟等级的,装着自己很懂似的,不装杀软,裸奔,导致今天在干伤天害理的事情的时候被病毒感染,全盘exe文件除了最核心的系统文件无一幸免。
这个专杀只是把文件的病毒区段数据填0,然后修改入口点,但是文件大小还是没恢复回去……因为感染数量相当巨大……这只是D盘的= =郁闷啊……
留下一个图片
我欠回炉重造了……唉。。。
下次再也不敢帮别人盗版了……= =
------------------------------------------------------------------------------------------------------------------
以上为去年10月1感染病毒当天的吐嘈
彻底败掉了啊……败给了一个2001年10月7日写的病毒……- -囧爆肝,既然经历了这么多年的时光还能在网络上进行传染,就说明很有分析的必要吧……
当然现在的杀毒软件都可以干掉的、打破了我两年裸奔不中毒的记录啊……- -唉。
好吧……现在开始拆它。
以感染的InternetExplore为例(版本号6.0.2900.5512)
感染前后PE头信息对比
感染后明显在后面多一段的
我们先来分析被感染程序的Loader
用到的IDA里的解码脚本
- -其实渣质量的.
mov eax, 0EAB8D//decode key
mov edx, 0041901C//解码起始位置
mov esi, 598//固定大小
L007: xor dword ptr [edx+esi], eax
dec esi
sub esi, 3
jnz L007
#include <idc.idc>
static decrypt(end, key)
{
auto i, x;
end=end+0x598;
for (i=0x598;i!=0;i=i-4)
{
x=Dword(end);
x= (x^key); PatchDword(end,x);
end = end - 4;
}
}
执行decrypt(0x41901c,0x0EAB8D)即可
既然是解码,我们就把这段叫作DecodeProc吧……解码段最后跳去执行Loader的main函数了
.qnk_:0041901C jnz short loc_419013
.qnk_:0041901E nop
.qnk_:0041901F nop
.qnk_:00419020 call main
.qnk_:00419020 DecodeProc endp
.qnk_:00419020
.qnk_:00419020 ; ---------------------------------------------------------------------------
Loader的流程图
Loader数据结构
0041902000017DE8CALL
0041902400000000------------------------------------------------
0041902800400000IEXPLORE.00400000;下面一砣是原程序和病毒体信息
0041902C00002451+0x0C OEP RVA
0041903000016C00+0x10 原大小
00419034000171D6+0x14 加密病毒Dll偏移
004190380002AA00+0x18 病毒Dll大小
0041903C00000000
0041904000401000+0x20 <&KERNEL32.LoadLibraryA>
0041904400402B4C+0x24 ASCII "UnhandledExceptionFilter"
0041904800402AE2+0x28 ASCII "GetCommandLineA"
0041904C00001AEC
0041905000002B4A+0x30 被替换掉的第一个IAT Thunk
0041905400002AE0+0x34 被替换掉的第二个IAT Thunk- -插一句,其实如果我早就注意到这里的话专杀的代码可以直接用这个数据、还能省掉减掉基址那一步……嘛,算了,反正效果一样、懒得去改了
0041905800000400
0041905C7C863E6Akernel32.UnhandledExceptionFilter; 被替换掉的Kernel32前两个函数
004190607C812FADkernel32.GetCommandLineA
Loader main函数的关键点
push ebp
mov ebp, esp
add esp, -140
mov eax, ebp
add eax, 4 ; eax=DATA after call
mov edx, ebp
push ebx
push esi
push edi
xor ebx, ebx
mov edi, dword ptr [eax] ; edi=DATA
lea ecx, dword ptr [ebp-38]
sub edi, 5 ; CALL addr
mov eax, dword ptr [edi+C] ; eax=oep rva
lea esi, dword ptr [edi+84]; esi=lpKernel32_dll
add eax, dword ptr [edi+8] ; [edi+8]base addr
add edx, 4
mov dword ptr [edx], eax ; 返回地址=oep
在InitImportTable函数里回填IAT并准备Loader之后要用到的IAT,下面的代码是用来回填IAT用的
push dword ptr [eax+24]
push hKernel32
call GetProcAddress
mov edx, dword ptr [ebp+C]
mov ecx, dword ptr [edx+20]
mov dword ptr [ecx], eax
mov eax, dword ptr [ebp+C]
push dword ptr [eax+28]
push hKernel32
call GetProcAddress
mov edx, dword ptr [ebp+C]
mov ecx, dword ptr [edx+20]
add ecx, 4
mov dword ptr [ecx], eax
剩下的几个关键函数用C代码的方式分析
bool __stdcall IsInfected(int a1, int a2, int a3)
{ if ( !RegOpenKeyEx(HKEY_CURRENT_USER,
a3 + 189, // Software\Microsoft\Windows\CurrentVersion\Explorer
0,
KEY_READ,
&hKey) )
{
bInfected = (*(int (__stdcall **)(HKEY, int, _DWORD, _DWORD, int, int *))(a2 + 48))(hKey, a3 + 240, 0, 0, a1, &v5) == 0;
(*(void (__stdcall **)(HKEY))(a2 + 52))(hKey);// RegQueryKeyValueExA(X,PINF...)
//病毒核心Dll的文件位置
}
return bInfected;
}
char ExpandVirus(int a1<ebx>, int a2<edi>, int a3<esi>, int a4, int a5, int a6)
{
GetModuleFileNameA(0, &v12, MAX_PATH, a2, a3, a1);
hFile = CreateFileA(&v12,GENERIC_READ,1,0,3,1,0);
if ( hFile == -1 )
{
BYTE3(v16) = 0;
}
else
{
GetTempPathA(MAX_PATH, &v12);
v15 = GetTickCount(); //生成随机文件名
v7 = 0;
do
{
v8 = *((_BYTE *)&v15 + v7);
*((_BYTE *)&v15 + v7++) = v8 / 10 + 97;
}
while ( v7 <= 2 );
BYTE3(v15) = 0;
GetTempFileNameA(&v15, v8 % 10, &v12, &v15, 0, a4);
v14 = CreateFileA(a4,GENERIC_WRITE|GENERIC_READ,1,0,2,128,0);
if ( v14 == -1 )
{
BYTE3(v16) = 0;
}
else
{ // 每次解码10KB病毒DLL,然后写完了继续解
v9 = *(_DWORD *)(a6 + 24);
(*(void (__stdcall **)(int, _DWORD, _DWORD, _DWORD))(a5 + 28))(hFile, *(_DWORD *)(a6 + 20), 0, 0);// SetFilePoint
for ( ; v9 > 0x2800; v9 -= 10240 )
{
(*(void (__stdcall **)(int, char *, signed int, char *, _DWORD))(a5 + 20))(hFile, &v11, 10240, &v13, 0);// ReadFile
Decode2(*(_DWORD *)(a6 + 128), &v11, 10240);// 解码病毒DLL
(*(void (__stdcall **)(int, char *, signed int, char *, _DWORD))(a5 + 24))(v14, &v11, 10240, &v13, 0);
}
(*(void (__stdcall **)(int, char *, unsigned int, char *, _DWORD))(a5 + 20))(hFile, &v11, v9, &v13, 0);//
// - -解码最后一块,完事擦屁股
Decode2(*(_DWORD *)(a6 + 128), &v11, v9);
(*(void (__stdcall **)(int, char *, unsigned int, char *, _DWORD))(a5 + 24))(v14, &v11, v9, &v13, 0);
(*(void (__stdcall **)(int))(a5 + 32))(v14);
BYTE3(v16) = 1;
}
CloseHandle(hFile);
}
return BYTE3(v16);
}
char __stdcall InitVirusDll(int a1, int a2, int a3, int a4)
{
v4 = (*(int (__stdcall **)(int))a2)(a1); // - -Load从输入表里得到的病毒路径
return v4
&& (v5 = (*(int (__stdcall **)(int, int))(a2 + 4))(v4, a4 + 245)) != 0// GetProcAddress(Initiate)
&& (unsigned __int8)((int (__stdcall *)(int))v5)(a3);// Initiate(Addr)
// 这里Addr是一开始DecodeProc执行完以后那个Call的地址
// 00419020 E8 7D010000 call 004191A2
// Loader分析告一段落。下面继续Dll分析
}
以上代码删除了大量的废物、不过这样看起来流程更清晰。
Loader分析到此结束。
------------------------------------------------------------------------------------------------------------------
病毒执行的流程是强制替换Kernel32前两个函数为LoadLibraryA和GetProcAddress,然后在运行的时候再还原。依据此原理,用C++编了个16K的程序。感染以后188KB……作者还真是不注意文件大小啊。这么大一砣东西粘到一个16K的程序上……蛋疼。
- -好吧,不扯淡了,继续……
下面是对于病毒核心Dll的分析
------------------------------------------------------------------------------------------------------------------
核心Dll中Initiate函数
char __cdecl Initiate(int a1)
{
hMutex = OpenMutexA(MUTEX_ALL_ACCESS, 0, "Residented");
if ( !hMutex || (result = sub_4019A8(v4, (int)&v3, 262)) != 0 && v3 < 7u && v3 >= 2u )
result = (unsigned int)SetWindowsHookExA(WH_CALLWNDPROC, (HOOKPROC)AttachHook, *(HINSTANCE *)off_459938, 0);// 放钩子,咬人
// 全局钩子,注入N多有CALLWNDPROC的进程
// 感谢气泡熊的解答^_^
if ( hMutex )
result = CloseHandle(hMutex);
if ( !v8 )
{
v7 = 8;
v9 = v4;
if ( v4 )
{
v10 = *(_DWORD *)v9;
v7 = 56;
result = (*(int (__fastcall **)(int, signed int))(*(_DWORD *)v9 - 4))(v9, 3);// call ika1.008FC65C
// CleanUp
v7 = 44;
}
if ( !v8 )
result = v6;
}
return result;
}
0012FCF0 00000004|HookType = WH_CALLWNDPROC
0012FCF4 008F1EBC|Hookproc = offset ika1.AttachHook
0012FCF8 008F0000|hModule = 008F0000 (ika1)
0012FCFC 00000000\ThreadID = 0
通过以下的流程图可以清楚地看到病毒做坏事的过程
这里关于InitInfectStruct函数初始化的结构体的数据结构有必要说一下……
008F4944008F4A14+0x00 pFileInfo
008F4948008F4AA8+0x04 p?(Size:0xC)
008F494C008F2268+0x08 pSelf
008F4950008F4AD0+0x0C pCurrentFileInfo(struct)
008F4954008F4B28+0x10 p?(Size:0x4)
008F4958008F4B34+0x14 pDecodeProc(Size:0xC)
008F495C00000000+0x18 BOOL bSuccess;
008F4960008F48C8+0x1C LPSTR lpInftectFile;
008F4964008F498C+0x20 p(size:0x84 - Status:inited by 0)
008F496800001800+0x24 HANDLE hFile;//(Infecting)
008F496C00000820+0x28 DWORD dwFileAttribute;
008F49706E633000+0x2C FILETIME CreationTime
008F497401CA9B55
008F497866265278+0x30 FILETIME LastAccessTime
008F497C01CAAD4D
008F49806E633000+0x34 FILETIME LastWriteTime
008F498401CA9B55
00874863 |.E8 70420400 call 008B8AD8 new
0087487D |.E8 1EDCFFFF call 008724A0;initSturct
__struct DecodeProc{
};(Size:0x14)
008F476C008F457C pSelf
008F477000000000
008F4774008F477C pDecodeCode
__struct FileInfo{
};(Size:0x14)
008F479C00000003+0x00 DWORD dwNumberOfSections;
008F47A0000000D8+0x04 DWORD e_lfanew;
008F47A4008F46CC+0x08 pSelf
008F47A8008F47B4+0x0C pNtHeader
008F47AC008F4910+0x10 pIMAGE_SECTION_HEADER
__struct pCurrentFileInfo{
};
008F4858008F46CC+0x00 pSelf
008F485C008F4870+0x04 LPSTR lpCurrentFilePath;
008F4860000000EC+0x08 HANDLE hFile;(CurrentFile)
008F48640000004A
008F486800000001
008F486C00000038
关于多态入口的生成函数、这里分析的比较少。总之就两种指令格式
push imm32
pop reg32
这是一种、还有
mov reg32,imm32
因为所使用的寄存器并不是固定的,前一种可以判断是否是0x68,后一种对应的机器码可以表示为X&0xB0=0xB0,具体参见专杀的代码。
008F477C 90 nop
008F477D 90 nop
008F477E 68 90909090 push 90909090
008F4783 5B pop ebx
008F4784 BF 90909090 mov edi, 90909090
008F4789 90 nop
008F478A 90 nop
008F478B 68 90909090 push 90909090
008F4790 5E pop esi
008F4791 90 nop
008F4792 90 nop
008F4793 90 nop
008F4794 90 nop
008F4795 90 nop
008F4796 90 nop
ASM Table
008C4C30 B8 BBB9BABE mov eax, BEBAB9BB
008C4C35 BF E8EBE9EA mov edi, EAE9EBE8
008C4C3A EE out dx, al
008C4C3B EF out dx, eax
下面这段是关于GetPEFileInfo函数的一点OD笔记、也可以结合着在IDA里分析出的函数名一起看
00873140 /$53 push ebx
00873141 |.8BD8 mov ebx, eax
00873143 |.8B43 0C mov eax, dword ptr [ebx+C]
00873146 |.8B4B 0C mov ecx, dword ptr [ebx+C]
00873149 |.0FB750 14 movzx edx, word ptr [eax+14] ;SizeOfOptionalHeader;
0087314D |.0FB741 06 movzx eax, word ptr [ecx+6] ;NumberOfSections;
00873151 |.8BC8 mov ecx, eax
00873153 |.8B43 0C mov eax, dword ptr [ebx+C]
00873156 |.C1E1 03 shl ecx, 3
00873159 |.0353 04 add edx, dword ptr [ebx+4]
0087315C |.8D0C89 lea ecx, dword ptr [ecx+ecx*4]
0087315F |.03D1 add edx, ecx
00873161 |.8B48 54 mov ecx, dword ptr [eax+54] ;SizeOfHeaders;
00873164 |.83C2 18 add edx, 18
00873167 |.2BCA sub ecx, edx
00873169 |.83F9 28 cmp ecx, 28
0087316C |.73 0E jnb short 0087317C
0087316E |.8B53 08 mov edx, dword ptr [ebx+8]
00873171 |.33C0 xor eax, eax
00873173 |.C742 18 03000>mov dword ptr [edx+18], 3
0087317A |.5B pop ebx
0087317B |.C3 retn
0087317C |>8B53 0C mov edx, dword ptr [ebx+C]
0087317F |.0FB74A 06 movzx ecx, word ptr [edx+6] ;NumberOfSections;
00873183 |.41 inc ecx
00873184 |.8BC1 mov eax, ecx
00873186 |.C1E0 03 shl eax, 3
00873189 |.8D0480 lea eax, dword ptr [eax+eax*4]
0087318C |.50 push eax
0087318D |.E8 E65A0400 call 008B8C78
00873192 |.59 pop ecx
00873193 |.8943 10 mov dword ptr [ebx+10], eax
00873196 |.8B53 0C mov edx, dword ptr [ebx+C]
00873199 |.8B43 08 mov eax, dword ptr [ebx+8]
0087319C |.0FB752 14 movzx edx, word ptr [edx+14] ;SizeOfOptionalHeader;
008731A0 |.0353 04 add edx, dword ptr [ebx+4]
008731A3 |.83C2 18 add edx, 18
008731A6 |.E8 39F7FFFF call 008728E4
008731AB |.8B4B 0C mov ecx, dword ptr [ebx+C]
008731AE |.8B53 10 mov edx, dword ptr [ebx+10]
008731B1 |.0FB741 06 movzx eax, word ptr [ecx+6]
008731B5 |.8BC8 mov ecx, eax
008731B7 |.8B43 08 mov eax, dword ptr [ebx+8]
008731BA |.C1E1 03 shl ecx, 3
008731BD |.8D0C89 lea ecx, dword ptr [ecx+ecx*4] ;读取节表
008731C0 |.E8 2FF7FFFF call 008728F4
008731C5 |.8B53 08 mov edx, dword ptr [ebx+8]
008731C8 |.B9 28000000 mov ecx, 28
008731CD |.8B43 08 mov eax, dword ptr [ebx+8]
008731D0 |.8B52 20 mov edx, dword ptr [edx+20]
008731D3 |.83C2 58 add edx, 58
008731D6 |.E8 19F7FFFF call 008728F4
008731DB |.8B43 0C mov eax, dword ptr [ebx+C]
008731DE |.0FB750 06 movzx edx, word ptr [eax+6]
008731E2 |.8913 mov dword ptr [ebx], edx
008731E4 |.B0 01 mov al, 1
008731E6 |.5B pop ebx
008731E7 \.C3 retn
剩下大量的关于核心Dll的分析请参见附件中的idb文件。
下面的是我写的专杀中用于判断是否感染病毒和进行病毒清除的核心代码
if (pSH->Name=='.'&&\
(pSH->Name-6==pSH->Name-3)&&\
(pSH->Name-3==pSH->Name)&&\
pSH->Name==0x7)
{
CString str;
str.Format("Infected file found on %s",sPath);
OutputDebugString(str);
pSH= IMAGE_FIRST_SECTION(pNtH);
DWORD dwKey,dwStartAddr;
dwStartAddr=rva2raw(pNtH->FileHeader.NumberOfSections, pSH, pNtH->OptionalHeader.AddressOfEntryPoint)+(DWORD)lpFile;
DWORD dwEnd;
dwEnd=(DWORD)lpFile+GetFileSize(hFile,0);
for (dwStartAddr;dwStartAddr<dwEnd;dwStartAddr++)
{
if((*((PBYTE)dwStartAddr)&0xB0)==0xB0||*(PBYTE)(dwStartAddr)==0x68)
{
dwKey=*(PDWORD)(dwStartAddr+1);
break;
}
}
for (dwStartAddr+=5;dwStartAddr<dwEnd;dwStartAddr++)
{
if((*((PBYTE)dwStartAddr)&0xB0)==0xB0||*(PBYTE)(dwStartAddr)==0x68)//mov edx,XXor push..
{
dwStartAddr=rva2raw(pNtH->FileHeader.NumberOfSections, pSH, *(PDWORD)(dwStartAddr+1)-pNtH->OptionalHeader.ImageBase)+(DWORD)lpFile;
break;
}
}
DWORD oep,imp1,imp2;
oep=dwStartAddr+0x10;
imp1=dwStartAddr+0x28;
imp2=dwStartAddr+0x2C;
oep=(*(PDWORD)oep)^dwKey;
imp1=(*(PDWORD)imp1)^dwKey;
imp1=/*rva2raw(pNtH->FileHeader.NumberOfSections, pSH,*/ imp1-pNtH->OptionalHeader.ImageBase;//);
imp2=(*(PDWORD)imp2)^dwKey;
imp2=/*rva2raw(pNtH->FileHeader.NumberOfSections, pSH,*/ imp2-pNtH->OptionalHeader.ImageBase;//);
str.Format("OEP RVA found at: 0x%.8X",oep);
OutputDebugString(str);
str.Format("First API name swap RVA found at : 0x%.8X\n",imp1);
OutputDebugString(str);
str.Format("Second API name swap RVA found at : 0x%.8X\n",imp2);
OutputDebugString(str);
pImportDescriptor=(PIMAGE_IMPORT_DESCRIPTOR)((DWORD)lpFile+ rva2raw(pNtH->FileHeader.NumberOfSections, pSH, pNtH->OptionalHeader.DataDirectory.VirtualAddress));
while(pImportDescriptor->FirstThunk)
{
char* dllname = (char*)((DWORD)lpFile + rva2raw(pNtH->FileHeader.NumberOfSections, pSH, pImportDescriptor->Name));
if (!stricmp(dllname,"kernel32.dll"))
{
pThunkData = (PIMAGE_THUNK_DATA)((DWORD)lpFile+ rva2raw(pNtH->FileHeader.NumberOfSections, pSH, pImportDescriptor->OriginalFirstThunk));
pThunkData->u1.Function = imp1-2;
pThunkData++;
pThunkData->u1.Function = imp2-2;
break;
}
else
pImportDescriptor++;
}
pNtH->OptionalHeader.AddressOfEntryPoint=oep;
pSH= IMAGE_FIRST_SECTION(pNtH)+pNtH->FileHeader.NumberOfSections-1;
dwSection=pSH->PointerToRawData;
memset(pSH,0,sizeof(PIMAGE_SECTION_HEADER));
pNtH->FileHeader.NumberOfSections--;
pNtH->OptionalHeader.SizeOfImage-=0x1000;
cntVirus++;
memcpy(pNewBuf,(PCHAR)lpFile+dwSection-4,4);
}
完全代码请参见附件中的ResidentKiller源代码。
--------------------------------------------------------------------------------
【经验总结】
我给这个病毒起个名字好了,综合网上查到的资料和各大杀毒引擎的结果,给这个病毒命名为Worm.Parite.Residented
VirusTotal扫描结果
http://www.virustotal.com/zh-cn/analisis/47bd590e99bee93bb0b46d7b74d60da3c931b8115122a4b450045591ea366399-1280672787
- -专杀的源码写的太挫了,大家就凑合着看吧、第一次在实战中应用dfs和bfs……应该算是吧……反正是学病毒里遍历磁盘的方法
非常感谢气泡熊的解答、还有啊cr。
附件目录以及Hash
病毒的主体思路:首先由感染病毒的文件中的Loader释放病毒Dll并调用Initiate函数,然后注入全局钩子,其中Explorer会在装载Dll时发动一个新的线程去做坏事、这样即使钩子卸掉了,病毒还是可以存活在Explorer中。然后分出来的线程负责感染硬盘exe和src文件、
终于做完专杀了……时间2010.7.31.21:59
专杀思路:UnHook掉所有类型为CALLWND的钩子,因为如果感染病毒的进程运行的话,很多的进程都会因为它的钩子而插入一个Dll,这样将导致病毒核心Dll无法被清除,然后结束掉两个有CALLWND的进程,这两个程序的Dll是没有办法用UnHook卸掉的、只能干掉。bfsFile一次搜索8个文件,避免CPU过高被发现,然后慢慢感染……
- -花了1天而已……开发速度已经比以前有了飞跃式的进展……当然以前也没有一次搞过这么长时间编程。
病毒分析告一段落了,反正专杀都做出来了,- -剩下的代码分析的太详细了反而不是很好。
第一次在实战中应用bfs和dfs啊……应该算是吧、
拖了这么长时间啊……- -真是太对不起你了,电脑啊……今天才给你复仇啊……
附件里有3个样本,一个是病毒的核心Dll,一个是经过原版(- -实际上我后来发现我只留了个解压缩过的Dll,原版已经找不到了,不过一样分析……哈哈~)Dll感染过的DeathStartup,一个是经过改良的(因为原版感染速度实在太糟糕了,bfsFile的时候8个文件中间就要隔N长时间。懒得等的同学可以使用第二个附件,No_Delay版本……)不这个版本对硬盘不是很友好的说……
就这样吧……皆さん、頑張つてください
--------------------------------------------------------------------------------
【版权声明】: 转载请注明作者并保持文章的完整, 谢谢!
2010年08月04日
Azure 支持一下~~~ 辛苦了啊啊== = = 完全看不懂 有待学习! 虽然看不懂,不过还是支持一下 初音未来 Parite这个老感染一直还有,感谢这么详细的分析! 支持楼主分析 牛X到不行了 楼主分析很强啊..很好很强大.