在本系列的文章中,对每一个病毒分析的最后一个部分,若无特殊情况,我都会采用逆向分析的手段来为读者彻底剖析目标病毒。但是之前的“熊猫烧香”病毒,我用了三篇文章的篇幅(每篇2500字左右)也仅仅分析了病毒的三分之一,而且还没分析到病毒的核心部分。主要也是因为那是我这个系列为大家分析的第一个病毒,为了将一些原理性的东西说清楚,所以文章略显冗长,也主要是照顾一下初学的朋友,摒弃那些高大上的东西,将我的实际分析过程完整地呈现出来。相信大家在认真阅读完那三篇文章后,都能够掌握基本的分析方法,那么我在以后的文章中,只会讨论病毒中的一些比较重要的部分,会跳跃式地讲解,略去无关紧要的内容。当然,我依旧会配上详尽的图片与文字进行说明,让大家仅仅是看这些文章,就宛如亲自动手了一样。
一般来说,病毒分析不会涉及到算法问题,如果是要分析算法(如我之前对于CM4注册机制的分析),那么我们更多地是需要关注程序的流程与逻辑,一般不深究CALL的具体内容。而病毒分析则往往需要搞清楚各个不同的CALL的意义,才能够弄清楚病毒的行为。所以本文的第一部分着重讲述对这些CALL的剖析。而第二部分则简单讨论一下进程守护技术的实现。
这里我们跳过程序的初始化部分,来到第一个API函数的位置:
图1
我们能够直接看到的第一个API函数是GetModuleFileName,这个函数用于获取当前进程已加载模块的文件的完整路径,该模块必须由当前进程加载。而该函数的返回值则是文件路径长度,由截图可见,该返回值保存在了EAX中,为2B,也就是说路径长度为2B个字符。可以看一下所返回的路径是什么。路径保存在图1中的“PathBuffer”中,跟踪该地址查看:
图2
可见程序已正确获取了当前文件的地址。然后继续分析下一个API函数:
图3
这里出现了ShellExecute这个函数,它的功能是运行一个外部程序(或者是打开一个已注册的文件、打开一个目录、打印一个文件等等),并对外部程序有一定的控制。具体到本程序,ShellExecute会运行Explorer.exe程序来打开“d:\”,其实也就是使用程序管理器打开D盘根目录。但是执行这个API函数是有条件的,它需要根据图3中第二行CALL语句的结果进行判定,那么有必要进入这个CALL,看看需要满足什么条件才能够执行ShellExecute。
进入oso.00403C48这个函数,可以看到如下代码:图4
程序会对EAX和EDX中的内容进行比对,其中EAX保存的字符串就是我们之前使用GetModuleFileName所获取的当前文件的路径,只不过被转化成了大写字符。而EDX保存的是D盘根目录下的OSO.EXE这个文件路径。二者在这里很明显是不同的。所以图4中黄色高亮显示的条件跳转语句也就不成立,程序会继续顺序执行:
图5
这里需要说明的是,由于本病毒是由Delphi编写的,那么字符串首地址减去4后,取出的4字节内容便是此字符串的长度。因此图5中的前两句代码意思就是获取两个路径的字符数,然后通过相减进行比较。这里很明显当前路径的字符数量是要大的,因此黄色高亮显示的条件跳转成立,来到oso.00403C6B的位置:
图6
这里依旧是字符的比较,由于二者不相等,所以上图中最后一句的条件跳转成立,来到oso.00403CD1的位置:
图7
这里比较的是盘符,也是不相等的,所以条件跳转成立,本函数也就执行完毕了。综合上述分析,这段函数的功用是判断当前所执行的文件是不是位于D盘的根目录下,如果是,则执行图3中的ShellExecute这个函数,反之则跳过这个函数执行。由于我们的这个程序是位于桌面上的,因此不执行ShellExecute函数。
接下来程序还会继续判断当前程序是否位于E、F、G、H、I盘根目录下,如果不是,那么也就不执行相应的ShellExecute函数。
之后程序会调用名为oso.004050F0的函数,进入其内部分析:
图8
可见病毒程序调用了GetSystemDirectory函数用于获取系统目录。一般来说,恶意程序使用这个函数的目的就是要将自身复制到系统目录中,以迷惑用户(详见《反病毒攻防研究第001篇:自我复制与自删除》)。之后病毒程序会将字符“severe.exe”与上面获得的系统目录字符串进行组合,新的路径也就是病毒程序需要隐藏的位置:
图9
之后可以看到CreateFile函数:
图10
但是这个CreateFile函数的执行是有条件的,它取决于上图中第一行代码中的CALL的返回值。进入这个CALL进行分析,可以找到:
图11
程序调用了FindFirstFile函数来查找系统目录中有没有severe.exe这个文件,如果没有(返回值为-1)则不执行图10中的CreateFile函数。由此可见,CreateFile函数在这里的作用不是创建文件,而是打开文件。接下来就是文件的复制操作:
图12
之后又是一系列的文件复制,病毒会将自身改名为tfidma.exe,并复制到系统目录中。还会将自身改名为conime.exe,复制到系统目录的drivers文件夹中。类似的操作不再赘述。之后程序就会再次调用ShellExecute函数,以执行所创建出来的这些程序。因此在“资源管理器”中就会出现severe.exe、conime.exe与tfidma.exe等进程。可以说在这个时候,我们的计算机就已经中病毒了。接下来我们会遇到线程的创建函数:
图13
CreateThread函数往往与Sleep或者WaitForSingleObject函数相配合使用。因为每个线程都有自己的CPU时间片,当主线程创建了新线程后,它的CPU时间片有时并没有完,它还可以继续执行。有时候如果主线程的代码非常少,那么在CPU指定的CPU时间片中主线程执行完后就退出了。主线程结束,那么意味着程序也就结束了,所以在这种情况下,我们自己创建的线程根本就没有被执行到。所以我们需要让主线程等待我们创建的线程,就需要使用Sleep或者WaitForSingleObject函数。本程序所采用的就是Sleep函数(等待1.3秒)。
回到图13中,根据OD对CreateThread函数的解析可以知道,该线程所调用的是oso.00404958这个函数。分析这个函数可以知道,它主要是创建了名为“hx1.bat”的批处理文件并执行,而该批处理的内容为:
@echo off set date=2004-1-22 ping ** localhost > nul date %date% del %0
其主要作用就是修改系统时间,测试本地网络系统并删除自身。病毒的常规分析部分就是这些。
进程的守护技术最早应该是源于“中国黑客病毒(worm.runouce)”,它开创性地采用了“三线程”结构。创建三线程就是为了更好地保护程序自身不被关闭和删除。我们可以将想要执行的代码放在主线程里,然后再生成两个辅助线程,它们的功能就是实现对程序的保护,防止程序被用户关闭或删除。在此,称我们的可执行文件的进程为主进程。两个辅助线程相互实时监视,如果监视对象被关闭了,就重新创建线程或进程。比如病毒程序可以选择Explorer.exe和Taskmgr.exe作为远程进程驻体。如果用户知道了远程线程的驻体为资源管理器后,就会打开任务管理器来结束Explorer,这时我们再把远程线程驻入到任务管理器中。也就是说,只要Explorer或Taskmgr有一个存在,就不可能结束主进程。如果有其它结束进程的工具,你就可以将其关闭掉,只要资源管理器和任务管理器均不存在时,就没有驻体来维持远程进程。不过,如果我们选择的远程进程为随机的,或者就是病毒自创的,这就不容易发现了。
这种三线程结构的程序框架大致如下(代码来自《浅析三线程程序开发思路与实现》):
// 1.主线程:main // 获得操作系统的系统目录 GetSystemDirectory(syspath,MAX_PATH); // 查询系统目录下病毒是否存在 FindFirstFile(virusname,&fdata); // 如果系统目录下没有,在将正在运行的程序复制到系统目录下 CopyFile(curname,tname,TRUE); // 在查询完毕后,关闭相关句柄 FindClose(ffhandle); // 打开系统目录下的文件 CreateFile(kname,GENERIC_WRITE,FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); // 修改时间 SetFileTime(fchandle,&ftime,NULL,&ftime); // 设置属性 SetFileAttributes(kname,FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM ); // 创建驻留在主进程内的辅助监视线程 CreateThread(NULL,0,watch,(LPVOID)rthread,0,NULL); // 2.本地辅助监视线程:watch // 以查询方式打开注册表的HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run RegOpenKeyEx(HKEY_LOCAL_MACHINE,rgspath,0,KEY_QUERY_VALUE,&hkey); // 查询是否存在virusname的键值 RegQueryValueEx(hkey,_T("virusname"),NULL,NULL,(LPBYTE)lpdata,&dwbuflen); // 如果没有相关键值,就以写方式再次打开注册表 RegOpenKeyEx(HKEY_LOCAL_MACHINE,rgspath,0,KEY_WRITE,&hkey); // 写入我们想要的东西,系统每次启动都会运行我们的可执行文件; RegSetValueEx(hkey,_T("virusname"),NULL,type,(const byte *)wtname,dwbuflen); // 获得远程线程的运行情况,看是否为STILL_ACTIVE,如果不是则创建远程线程 GetExitCodeThread(wethread,&exitcode); // 3.远程线程:remote // 以所有可能的访问方式打开主进程,以便监视主进程的运行情况 tOpenProcess(PROCESS_ALL_ACCESS,FALSE,erp->rpmousepid); // 等待直到主进程结束 tWaitForSingleObject(erp->rpprocesshandle,INFINITE); // 重新启动我们的可执行文件 tWinExec(erp->rpwinexecname, 0); // 4.获得进程ID:processtopid // 列举所有的进程 EnumProcesses(lpidprocesses,sizeof(lpidprocesses),&cbneeded); // 以查询信息和读取的方式打开进程 OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,FALSE,lpidprocesses[i]); //获得进程模块的句柄 EnumProcessModules(hprocess,&hmodule,sizeof(hmodule),&cbneeded); // 获得特定模块的名字,以备比较 GetModuleBaseName(hprocess,hmodule,normalname,sizeof(normalname)); // 5.创建远程线程:createremote // PROCESS_CREATE_THREAD for CreateRemoteThread // PROCESS_VM_OPERATION for VirtualAllocEx // PROCESS_VM_WRITE for WriteProcessMemory OpenProcess(PROCESS_CREATE_THREAD|PROCESS_VM_OPERATION|PROCESS_VM_WRITE,FALSE,remotepid); // 在远程进程中分配空间,以备将线程代码置入其中 VirtualAllocEx(rphandle,NULL,cb,MEM_COMMIT,PAGE_EXECUTE_READWRITE); // 将远程线程remote的代码写入到远程进程的地址空间中 WriteProcessMemory(rphandle,remotethr,(LPVOID)remote,cb,NULL); // 将远程线程所需的参数也写入到远程进程的地址空间中 WriteProcessMemory(rphandle,remotepar,(LPVOID)&rp,cb,NULL); // 创建远程监视线程 CreateRemoteThread(rphandle,NULL,0,(LPTHREAD_START_ROUTINE)remotethr,(LPVOID)remotepar,0,NULL);
毕竟本系列不是教大家编写病毒,而是剖析病毒的大概思路,所以上述程序大家有个大概的印象即可,这样在以后的实际分析中,就能有大概的应对思路。
至此,QQ盗号木马(oso.exe)病毒程序的分析就到这里。其实这几篇文章也就主要讨论了进程守护技术的实现与杀除。在以后的文章中,我只会讨论病毒中一些有特色的部分,也会与时俱进,研究一些目前流行的病毒,希望大家喜欢。