行为分析如下: 四:样本分析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函数获取函数地址,然后调用即可。代码如下:
1234567891011HMODULE 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如下:
123456struct 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是因为需要兼顾之前的版本情况。
1234567891011struct 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 Avirtual fn in B`.为什么会产生这样的结果呢,因为A类是基类,B类是派生类。首先创建了A类,当调用fn()这个常规成员函数的时候,直接调用即可。但是调用v_fn的时候,由于v_fn是一个虚函数。会事先判断B类这个派生类的v_fn是否可用,可用的话直接调用B类的v_fn。