病毒分析技术18) ---Ghost后门病毒

前言

         后门病毒的前缀是:Backdoor。该类病毒的特性是通过网络传播,给系统开后门,给用户电脑带来安全隐患。2004年年初,IRC后门病毒开始在全球网络大规模出现。一方面有潜在的泄漏本地信息的危险,另一方面病毒出现在局域网中使网络阻塞,影响正常工作,从而造成损失。即使管理员通过改变所有密码之类的方法来提高安全性,仍然能再次侵入,使再次侵入被发现的可能性减至最低。
大多数后门设法躲过日志,大多数情况下即使入侵者正在使用系统也无法显示他已在线。摘录于百度百科
         此次病毒多次使用隐藏技术,反复转移文件,以及使用Hook技术对抗分析。但是美中不足的是,释放样本的时候没有加密资源,直接获得了payload。

一:目录

  • 1.目录
  • 2.Ioc
  • 3.行为分析
  • 4.样本分析
  • 5.技术总结

二:IoC

2.1:样本1

  • 1.样本名称:DD668456CF2F3B72773D1968487BDCD5
  • 2.样本md5:dd668456cf2f3b72773d1968487bdcd5
  • 3.是否加壳:Microsoft Visual C++ 6.0 [Overlay]
  • 4.编译语言:vc++
  • 5.样本来源:来自于网络收集

2.2:样本2

  • 1.样本名称:netsvcs_0x0ex1.dll
  • 2.样本md5:5b047ba6518480625dcbe5a14e10dc87
  • 3.是否加壳:Microsoft Visual C++ ver. 6/7 DLL
  • 4.编译语言:vc++
  • 5.样本来源:样本1释放而来

三:行为分析

  • 行为分析如下:

    四:样本分析

    4.1 样本1

  • 1.首先读取进程镜像文件中倒数1024个字节,这些内容部分然后使用base和普通运算加密形成以后的服务名称Microsoft Device Manager。


  • 2.判断参数是否是Gh0st Update,如果不是立即退出进程
  • 3.当程序参数为Gh0st Update,首先判断添加ACE到指定的ACL,目的用于控制访问数据流量。

  • 4.接下来释放一个tmp文件,然后找到他的导出表,执行ResetSSDT,初步怀疑重置SSDT是为了干扰安全软件的分析和查杀,尤其是一些主动防御的安全软件。具体步骤是这样的:
    • 首先遍历资源文件,释放其中的bin文件命名为res.tmp,然后移动文件命名为ex.tmp设置一个新的文件时间以及隐藏属性并删除之前的res.tmp。


    • 获取其中的ResetSSDT函数并调用

  • 5.接下来,创建名为Microsoft Device Manager的服务,具体的步骤如下:
    • 首先读取CurrentVersion\Svchost下netsvcs项的内容x,然后读取SYSTEM\CurrentControlSet\Services\x,并删除C:\WINDOWS\system32\xex.dll这个文件,以便替换,替换成netsvcs_0x0ex.dll
    • 然后创建一个名为Microsoft Device Manager的服务
    • 创建互斥体,目的是保证实例唯一性
  • 6.然后将之前的ex.tmp文件写入名为SYSTEM\CurrentControlSet\Services\netsvcs_0x0的项InstallModule的内容。接下来就开启服务

    4.2 样本2

  • 1.首先这个是一个dll文件,根据之前的分析,我们需要调试服务,首先我们不能用OD自带的loaddll因为这默认的入口点是dllMain函数,而调式服务的入口点是ServiceMain函数,我们利用GetProcAddress函数获取函数地址,然后调用即可。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    HMODULE hModule=NULL;
    const char ServiceName[]="ServiceName2";
    const char *ServiceName3=ServiceName;
    typedef int(*Fun)(int, const char**);
    hModule = LoadLibrary("netsvcs_0x0ex.dll");
    if(hModule==NULL)
    printf("Load Dll Fail");
    Fun ServiceMain=(Fun)GetProcAddress(hModule,"ServiceMain");
    ServiceMain(1,&ServiceName3);
    getchar();
    return 0;
  • 2.首先挂起服务,将dwCheckPoint成员设置为0,表示挂起服务或者服务暂停。

  • 3.获取\CurrentControlSet\Services\ServiceName2的Type子项下的内容,如果内容为288,则在后门执行完毕后进入休眠状态。

  • 4.读取文件中最后1024个字节内容,这是为了验证是否为正确的恶意文件。以及创建新的互斥量和Ipaddress。
  • 5.如果服务存在,需要重置SSDT以及修改服务子项Type的内容为288,接着删除删除InstallModule键内容,删除InstallModule对应的文件

  • 6.将之前从文件中获取rqaxva61p72uvaenqaevp6ef经过Base64以及普通加密运算得到Ipaddress192.168.1.88:8088


  • 7.接着通过之前编码的Ipaddress192.168.1.88:8088进行链接,然后利用socket进行信息传输


  • 8.然后获取系统相关信息,主要用系统版本,主机名,CPU频率,驱动信息,然后将这些数据发送给病毒作者服务器。

    • 系统版本信息
    • 主机名:
    • CPU频率
    • 驱动信息
  • 9.病毒的后门控制模块有以下功能:

    • 1.主要是获取磁盘和文件信息

    • 2.屏幕控制(包含视屏和音频)




    • 3.管道通信读取\syslog_dat数据



    • 4.发送\syslog_dat数据
    • 5.获取进程的PID,模块信息和进程映像文件

    • 6.提升进程权限为关机权限

    • 7.执行之前释放的文件

    • 8.Update本地的后门程序
    • 9.劫持winlogon.exe,首先判断注册表下services下的内容是否为288,如果是288则删除注册表和文件,如果不是就劫持winlogon.exe,已知winlogon进程随着开机自启而启动,如果劫持了此进程,相当于病毒拥有了自启的功能。




    • 10.清空软件日志
    • 11.打开IE

    • 12.删除Services下的注册表内容
    • 13.钩取特殊字符信息,fn是一个回调函数,保存于syslog



      五:技术总结

               技术是招式,编程是内功,所以以后的分析我尽可能总结点编程的东西,而不单单只是分析。这样才能更好的帮助我们理解分析。

      socket编程基础

               WinSock 编程的第一步就是加载 ws2_32.dll,然后调用 WSAStartup() 函数进行初始化,并指明要使用的版本号。
               接下来就是创建套接字的过程,使用socket()函数创建套接字。函数原型如下:int socket( int af, int type, int protocol)。在windows创建套接字SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); //创建TCP套接字或者SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字
  • 参数af的意思是Address Family,也就是IP地址类型,有两种形式 AF_INET 和 AF_INET6,表示IPV4和IPV6,

  • 参数Type有两种,常用的有SOCK_STREAM 和 SOCK_DGRAM,分别是有连接的和无连接的。
  • 参数protocol表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。
             然后在客户端用connect函数去连接,服务端用bind()去绑定。在windows下连接的方法如下:int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen); //Windows
  • 参数sock是之前创建的套接字句柄
  • 参数serv_addr是sockaddr 结构体变量的指针
  • 参数addrlen是addr变量的大小
             其中第二个参数最重要,他指向的结构体sockaddr_in如下:

    1
    2
    3
    4
    5
    6
    struct sockaddr_in{
    sa_family_t sin_family; //地址族(Address Family),也就是地址类型
    uint16_t sin_port; //16位的端口号
    struct in_addr sin_addr; //32位IP地址
    char sin_zero[8]; //不使用,一般用0填充
    };
  • 其中sin_addr是struct in_addr结构体类型的变量。之所以在结构体里面套用一个结构体而不直接用变量s_addr是因为需要兼顾之前的版本情况。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    struct in_addr{
    in_addr_t s_addr; //32位的IP地址
    };
    ```
             然后是发送和接收数据,在windows下,使用send发送数据,原型如下:它的原型为:
    `int send(SOCKET sock, const char *buf, int len, int flags);`以及使用recv函数接收数据`int recv(SOCKET sock, char *buf, int len, int flags);`要注意客户端和服务端是相对的,也就是说客服端不单单只是发送数据也存在接收数据的函数。
    * 参考资料:[http://c.biancheng.net/cpp/html/3038.html](http://c.biancheng.net/cpp/html/3038.html)
    ## C++虚函数分析
             所谓虚函数,就是在某基类中声明为virtual,并在一个或多个派生类中被重新定义的成员函数。用于实现多态性,通过指向派生类的基类指针,访问派生类中同名覆盖成员函数。也就是说,在编译的时候并不知道会调用那个函数,确定函数的真实调用过程是动态的。
             如下代码,运行结果是`fn in A
    virtual fn in B`.为什么会产生这样的结果呢,因为A类是基类,B类是派生类。首先创建了A类,当调用fn()这个常规成员函数的时候,直接调用即可。但是调用v_fn的时候,由于v_fn是一个虚函数。会事先判断B类这个派生类的v_fn是否可用,可用的话直接调用B类的v_fn。

class A {
public:
void fn() { printf(“fn in A\n”); }
virtual void v_fn() { printf(“virtual fn in A\n”); }
};
class B : public A {
public:
void fn() { printf(“fn in B\n”); }
virtual void v_fn() { printf(“virtual fn in B\n”); }
};
int main() {
A *a = new B();
a->fn();
a->v_fn();
return 0;
}
```
         如图,虚函数不是一开始就存在的,他是依靠虚函数表指引调用虚函数,每个类如果虚函数大于1都会构建一个虚函数表。事先会使用一个构造函数去动态指向将要调用的虚函数,返回值为函数指针,放入eax中,后期call eax即可。