病毒样本分析大致分为两种,一种是行为分析,一种是逆向分析。
行为分析主要是通过系统监控软件,监控系统中各资源或环境的变化,比如监控注册表、监控文件、监控进程,以及监控网络等。当然了,还可以通过 HIPS 软件来监控病毒的行为。各类系统监控工具或者是 HIPS 软件都是在系统的不同层面上进行了不同方式的 HOOK。比如说在内核层进行 HOOK,在应用层进行 HOOK。HOOK 的方式也是多样化的,比如直接 HOOK API 函数,或者 HOOK SSDT 表中的内核函数。
逆向分析主要是通过静态分析或者动态调试来查看病毒的反汇编代码,通过断点或者单步来观察病毒的内存数据、寄存器数据等相关内容。
行为分析可以快速的确定病毒的行为从而写出专杀工具,但是对于感染型的病毒是无法通过行为分析进行分析的,或者病毒需要某些触发条件才能执行相应的动作,这样因为系统环境的因素,也无法通过行为分析得到病毒的行为特征。逆向分析通过查看病毒的各个分支流程可以完整的、全面的查看病毒的各个流程,包括病毒需要在某些条件下才被触发的流程,都可以通过查看反汇编代码进行查看。但是,对于病毒的逆向分析需要有 Windows 的开发能力,需要有阅读汇编的代码。相对于行为分析来讲,要求的技术含量更高一些。
我们通过一个真实的病毒样本,进行一次逆向分析,希望可以对病毒分析的入门者有一定的帮助。
下载到样本后,放置到虚拟机中,虚拟机最好也处于断网情况,因为我们不确定病毒到底有哪些行为。由于我们是逆向分析,即使一些需要连网后才有的病毒行为,我们也能通过反汇编代码一览无余。放置在虚拟机中后,我们例行用 PEID 查壳之。幸运的是这个病毒没有,而且是用VC编写的。
由于无壳,省去了脱壳的步骤。我们用 OD 载入病毒,由于是 VC 编写的直接跳过 VC 的启动函数,来到真正的病毒代码处,有点分析经验的人都能一眼看出哪些部分是 VC 的启动代码,关于如何跳过病毒的启动代码就不介绍了。我们直接来到病毒的代码处,病毒的代码如下所示(为了保证代码的美观,我去掉了 OD 中机器码的那一列,把注释列放到了对应代码的上一行):
00401457 PUSH EBP
00401458 MOV EBP,ESP
0040145A SUB ESP,418
00401460 PUSH EBX
00401461 PUSH ESI
00401462 PUSH EDI
00401463 CALL 00401160
00401457 地址处是病毒代码的开始位置,也就是我们在用 VC 写代码是的 main() 函数处。在 00401463 地址处的代码 CALL 00401160 是调用了一个函数,我们查看被调用函数的代码,代码如下:
00401160 PUSH EBP
00401161 MOV EBP,ESP
00401163 SUB ESP,1C
00401166 LEA EAX,DWORD PTR SS:[EBP-4]
00401169 PUSH EAX
0040116A PUSH 28
; kernel32.GetCurrentProcess
0040116C CALL DWORD PTR DS:[<&KERNEL32.GetCurrent>]
00401172 PUSH EAX
; ADVAPI32.OpenProcessToken
00401173 CALL DWORD PTR DS:[<&ADVAPI32.OpenProces>]
00401179 TEST EAX,EAX
0040117B JE SHORT 004011CC
0040117D LEA EAX,DWORD PTR SS:[EBP-C]
00401180 PUSH EAX
; ASCII "SeDebugPrivilege"
00401181 PUSH 00403148
00401186 PUSH 0
; ADVAPI32.LookupPrivilegeValueA
00401188 CALL DWORD PTR DS:[<&ADVAPI32.LookupPriv>]
0040118E TEST EAX,EAX
00401190 JNZ SHORT 0040119D
00401192 PUSH DWORD PTR SS:[EBP-4]
; kernel32.CloseHandle
00401195 CALL DWORD PTR DS:[<&KERNEL32.CloseHandl>]
0040119B LEAVE
0040119C RETN
从代码中我们可以看出,00401160 地址处的函数依次调用了如下的 API 函数,分别是:GetCurrentProcess()->OpenProcessToken()->LookupPrivilegeValueA()->CloseHandle()。从前三个 API 函数我们可以看出,这个函数的作用是调整当前进程的权限,我们配合 00401181 地址处入栈的字符串 “SeDebugPrivilege” 可以看出,这个函数将当前进程的权限调整为具有调试的权限。
回到前面继续看我们病毒的主流程,接着上面的代码如下:
00401468 MOV ESI,103
; [ebp - 418]保存系统目录
0040146D LEA EAX,DWORD PTR SS:[EBP-418]
00401473 PUSH ESI
00401474 PUSH EAX
; kernel32.GetSystemDirectoryA
00401475 CALL DWORD PTR DS:[<&KERNEL32.GetSystemDirector>]
; [ebp - 314]保存Windows目录
0040147B LEA EAX,DWORD PTR SS:[EBP-314]
00401481 PUSH ESI
00401482 PUSH EAX
; kernel32.GetWindowsDirectoryA
00401483 CALL DWORD PTR DS:[<&KERNEL32.GetWindowsDirecto>]
; [ebp - 10c]保存当前病毒文件的路径(包含病毒文件名)
00401489 LEA EAX,DWORD PTR SS:[EBP-10C]
0040148F PUSH ESI
00401490 XOR EBX,EBX
00401492 PUSH EAX
00401493 PUSH EBX
; kernel32.GetModuleFileNameA
00401494 CALL DWORD PTR DS:[<&KERNEL32.GetModuleFileName>]
; MSVCRT.strrchr
0040149A MOV EDI,DWORD PTR DS:[<&MSVCRT.strrchr>]
004014A0 LEA EAX,DWORD PTR SS:[EBP-10C]
004014A6 PUSH 5C
004014A8 PUSH EAX
; 从右找出第一个"\"的位置
004014A9 CALL EDI
004014AB POP ECX
004014AC CMP EAX,EBX
004014AE POP ECX
004014AF JE SHORT 004014B3
; 得到病毒的当前路径
004014B1 MOV BYTE PTR DS:[EAX],BL
004014B3 PUSH 1
; 此处是对麦克风设备进行设置,这里没有详细看
004014B5 CALL 004016AC
004014BA POP ECX
004014BB PUSH EBX
004014BC PUSH EBX
004014BD PUSH EBX
004014BE PUSH 00401349
004014C3 PUSH 400
004014C8 PUSH EBX
; kernel32.CreateThread
004014C9 CALL DWORD PTR DS:[<&KERNEL32.CreateThread>]
看到该处反汇编代码处,我们可以看出,病毒获得了系统目录及 Windows 目录的路径,还有病毒的当前路径,以及病毒的文件名。在 004014c9 地址处,调用了 CreateThread() 函数,该函数用来创建一个线程。CreateThread() 函数的函数原型如下:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTESlpThreadAttributes, // SD
DWORD dwStackSize, // initial stack size
LPTHREAD_START_ROUTINElpStartAddress, // thread function
LPVOID lpParameter, // thread argument
DWORD dwCreationFlags, // creation option
LPDWORD lpThreadId // thread identifier
);
CreateThread() 函数的第三个参数是线程函数的地址,由于 WIN32 API 的调用方式是 stdcall 方式,因此函数的入栈方式是从右至左,那么线程的函数地址是在 004014be 的指令给出,该处的指令为 push 00401349。既然知道了线程函数的地址是 401349,那么我们就看一下这个线程函数的代码完成了什么样的功能,代码如下:
00401349 PUSH EBP
0040134A MOV EBP,ESP
0040134C PUSH ECX
0040134D PUSH ECX
0040134E PUSH EBX
0040134F PUSH ESI
00401350 PUSH EDI
00401351 NOP
; USER32.SendMessageA
00401352 MOV ESI,DWORD PTR DS:[<&USER32.SendMessageA>]
……
00401367 PUSH EDI
; ASCII "AVP.AlertDialog"
00401368 PUSH 00403134
; USER32.FindWindowA
0040136D CALL DWORD PTR DS:[<&USER32.FindWindowA>]
……
00401384 PUSH EDI
00401385 PUSH EAX
; USER32.FindWindowExA
00401386 CALL DWORD PTR DS:[<&USER32.FindWindowExA>]
……
004013AA PUSH EDI
004013AB PUSH EDI
004013AC PUSH DWORD PTR SS:[EBP-8]
; USER32.FindWindowExA
004013AF CALL DWORD PTR DS:[<&USER32.FindWindowExA>]
……
; USER32.FindWindowExA
004013D8 CALL DWORD PTRDS:[<&USER32.FindWindowExA>]
……
004013FD PUSH EDI
004013FE PUSH DWORD PTR SS:[EBP-8]
; USER32.FindWindowExA
00401401 CALL DWORD PTR DS:[<&USER32.FindWindowExA>]
……
00401420 PUSH EDI
; ASCII "AVP.Product_Notification"
00401421 PUSH 004030DC
; USER32.FindWindowA
00401426 CALL DWORD PTR DS:[<&USER32.FindWindowA>]
……
0040143C PUSH EDI
; USER32.FindWindowA
0040143D CALL DWORD PTR DS:[<&USER32.FindWindowA>]
……
0040144F PUSH EAX
00401450 CALL ESI
00401452 JMP 0040135F
从该反汇编代码处我们能看出,整个线程函数处于一个死循环中,因为函数的最后一句代码 00401452 jmp 0040135f 是一个向上的跳转指令。在循环中基本上只完成了一个任务,也就是不断的查找(查找窗口调用的是 FindWindowEx() 函数)某个窗口,只要找到那个窗口就调用 SendMessage 函数向它发送消息。通过它查找的窗口是 “AVP.AlertDialog” 和 “AVP.Product_Notification”,说明这段代码是通过发送消息来躲过特定杀毒软件的主动防御。我们接着看病毒的主要流程,代码如下:
004014CF LEA EAX,DWORD PTR SS:[EBP-314]
; windows目录
004014D5 PUSH EAX
004014D6 LEA EAX,DWORD PTR SS:[EBP-10C]
; 病毒当前目录
004014DC PUSH EAX
; MSVCRT._stricmp
004014DD CALL DWORD PTR DS:[<&MSVCRT._stricmp>]
004014E3 POP ECX
004014E4 TEST EAX,EAX
004014E6 POP ECX
; 这个分支很重要,它判断病毒的所在位置
004014E7 JNZ 00401612
这段代码是判断病毒的当前位置,病毒的当前位置决定了病毒的流程走向。这是在病毒中一个很重要的分支。由于目前病毒不处于 C:\windows 目录下,那么流程必然会跳走,我们先来看跳走的流程,返回来在继续查看未跳走的流程。
; MSVCRT.sprintf
00401612 MOV EDI,DWORD PTRDS:[<&MSVCRT.sprintf>]
00401618 LEA EAX,DWORD PTRSS:[EBP-314]
; ASCII "mppds.exe"
0040161E PUSH 00403020
00401623 PUSH EAX
00401624 LEA EAX,DWORD PTRSS:[EBP-210]
; ASCII"%s\%s"
0040162A PUSH 0040307C
0040162F PUSH EAX
00401630 CALL EDI
00401632 ADD ESP,10
; 保存病毒路径
00401635 LEA EAX,DWORD PTRSS:[EBP-10C]
0040163B PUSH ESI
0040163C PUSH EAX
0040163D PUSH EBX
; kernel32.GetModuleFileNameA
0040163E CALL DWORD PTRDS:[<&KERNEL32.GetModuleFileNameA>]
00401644 LEA EAX,DWORD PTRSS:[EBP-210]
0040164A PUSH EBX
0040164B PUSH EAX
0040164C LEA EAX,DWORD PTRSS:[EBP-10C]
00401652 PUSH EAX
;kernel32.CopyFileA
00401653 CALL DWORD PTRDS:[<&KERNEL32.CopyFileA>]
; kernel32.GetCurrentProcessId
00401659 CALL DWORD PTRDS:[<&KERNEL32.GetCurrentProcessId>]
0040165F PUSH EAX
00401660 LEA EAX,DWORD PTRSS:[EBP-10C]
00401666 PUSH 40
00401668 PUSH EAX
00401669 LEA EAX,DWORD PTRSS:[EBP-210]
0040166F PUSH 40
00401671 PUSH EAX
00401672 8D85 ECFCFFFF LEA EAX,DWORD PTR SS:[EBP-314]
; ASCII "%s %c%s%c%d"
00401678 PUSH 004030B4
0040167D PUSH EAX
0040167E CALL EDI
00401680 ADD ESP,1C
00401683 LEA EAX,DWORD PTRSS:[EBP-314]
00401689 PUSH EBX
; 以带参数的方式启动拷贝到C盘的mppds.exe文件
0040168A PUSH EAX
; kernel32.WinExec
0040168B CALL DWORD PTRDS:[<&KERNEL32.WinExec>]
上面的反汇编代码调用了 CopyFile() 函数将自身拷贝到 Windows 目录下,并且调用 WinExec() 函数启动拷贝到 Windows 目录下的 mppds.exe,在启动的时候为 mppds.exe 带了参数,对 WinExec() 的调用栈如下所示:
0012FAF8 0012FC10 |CmdLine ="C:\WINDOWS\mppds.exe @C:\Documents and Settings\Administrator\桌面\4sy.exe@1488"
0012FAFC 00000000 \ShowState = SW_HIDE
我们关闭当前 OD,不要让病毒以该参数启动,我们启动另外一个 OD 来调试 Windows 目录下的 mppds.exe ,并且调试时带上如上的参数。我们直接运行到那个关键的跳转处,继续往下执行,反汇编代码如下:
; kernel32.GetCommandLineA
004014ED CALL DWORD PTRDS:[<&KERNEL32.GetCommand>]
……
; 省略了中间的代码是得到参数中病毒原来的位置
00401542 ADD ESP,18
00401545 MOV BYTE PTR SS:[EBP+EDI-10D],BL
0040154C PUSH DWORD PTR SS:[EBP-4]
0040154F PUSH EBX
00401550 PUSH 1F0FFF
; kernel32.OpenProcess
00401555 CALL DWORD PTRDS:[<&KERNEL32.OpenProces>]
……
00401561 PUSH EBX
00401562 PUSH EDI
; kernel32.TerminateProcess
00401563 CALL DWORD PTRDS:[<&KERNEL32.TerminateP>]
00401569 PUSH -1
0040156B PUSH EDI
; kernel32.WaitForSingleObject
0040156C CALL DWORD PTRDS:[<&KERNEL32.WaitForSin>]
00401572 LEA EAX,DWORD PTR SS:[EBP-10C]
00401578 PUSH EAX
; kernel32.DeleteFileA
00401579 CALL DWORD PTRDS:[<&KERNEL32.DeleteFile>]
0040157F TEST EAX,EAX
00401581 JNZ SHORT 0040158D
00401583 PUSH 1
; kernel32.Sleep
00401585 CALL DWORD PTRDS:[<&KERNEL32.Sleep>]
0040158B JMP SHORT 00401572
上面的代码是 GetCommandLine() 函数得到病毒的原来的位置,然后通过 OpenProcess() 和 TerminateProcess() 两个函数结束掉原来病毒的进程,然后调用 DeleteFile() 函数删除原来的病毒。
; ASCII "explorer.exe"
0040158D PUSH 004030C4
; 该函数用来查找explorer.exe进程的PID
00401592 CALL 00401000
……
; 释放mppds.dll文件
004015CD CALL 0040109C
……
; 注入mppds.dll到explorer.exe进程
004015E6 CALL 004011F9
……
; 将mppds.exe写入注册表的启动项
00401609 CALL 004017A5
……
; 病毒的另一个关键分支执行完毕
00401610 JMP SHORT 00401691
00401592 地址处的 CALL 是调用了一个函数,其函数的作用是获得 explorer.exe 进程的 PID,在其反汇编代码中依次调用了几个函数:
CreateToolhelp32Snapshot()->Process32First()->Process32Next()->CloseHandle(),这个是个典型的遍历系统进程的代码,而在遍历的过程中始终在拿进程名与 “explorer.exe” 进行比较,如果相等的会则结束遍历过程,并返回 explorer.exe 进程的 PID。
004015cd 处的 CALL 调用的函数是释放一个 DLL 文件到系统路径,其反汇编代码中依次调用了如下几个函数:
FindResource()->SizeofResource()->LoadResource()->SetHandleCount()->fopen()->fwrite()->fclose()。这也是一个典型的通过资源释放文件的过程,该过程从 mppds.exe 文件的资源中释放出 mppds.dll 文件到系统目录下。
004015e6 地址处的 CALL 调用的函数是将释放出来的 mppds.dll 文件注入到 explorer.exe 进程中,也就是说地址 401592 的 CALL 和地址 15cd 的 CALL,是为当前地址的注入做准备的。在分析代码时我们可以看一些主要的代码,整个功能也就明白了,004015e6 地址处 CALL 的函数中调用了 CreateRomoteThread() 函数,通过这一个函数我们就能确定该函数的目的是用来进行注入的。
00401609 地址处的 CALL 是将病毒的加入到注册表的启动项中,因为在这个 CALL 中调用了注册表操作相关的函数,另外配合几个字符串常量,我们就很容易的发现了该函数的作用了。
到这里,整个病毒的功能我们就分析完成了,至于释放出来的 mppds.dll 的功能这里就不再继续分析了。我们这里完整的分析了一个病毒,从我们分析的过程中可以看出,熟悉 Win32 API 和一些编程知识对于我们分析病毒是非常有帮助的。在反病毒厂商招聘病毒分析师时其中就要求熟悉 Win32 API、C 语言、汇编语言等知识。如果有对反病毒有兴趣的朋友,可以到一些反病毒网站参考一下他们关于病毒分析的招聘要求,就可以有方向性的进行学习了。希望这篇文章能给你带来帮助。
注:文章时很早以前写的,病毒样本已经找不到了,而且提供病毒样本也违法。
评论